Free Web Hosting by Netfirms
Web Hosting by Netfirms | Free Domain Names by Netfirms

 

T E X T   V E R S I O N

[a doctor]

Allegro GUI Clinic
Building and Using Dialogs
 
  GUI Clinic Home
 
  How do I Put Dialogs into My Program?
The DIALOG Structure | Standard Dialog Controls
Creating and Running a Dialog
 
 

Previous: Getting Started

How do I Put Dialogs into My Program?

The GUI system does its gooey job by running what are inventively called "dialogs." These dialogs are really arrays of structures called DIALOGs. More on the DIALOG structure follows.

Putting dialogs into your program is simple. First, you must create a dialog. Then, you run the dialog by calling do_dialog(dialog, focus_obj). That's it! Of course, creating a dialog can be a little tricky; it depends on what you want it to do. It also depends on how well you know the DIALOG structure. Let's take a look at that, shall we?

The DIALOG Structure

Here's what a DIALOG looks like, from the Allegro documentation:

   typedef struct DIALOG
   {
      int (*proc)(int, DIALOG *, int);
      int x, y, w, h;
      int fg, bg;
      int key;
      int flags;
      int d1, d2;
      void *dp, *dp2, *dp3;
   } DIALOG;

What do all these fields do? Here's the scoop:

int (*proc)(int, DIALOG *, int)
The proc pointer is the most important. Let's save that for last.

int x, y, w, h
The x, y, w, and h fields specify the DIALOG object's position and size in pixels. Simple. The position numbers are relative to the top-left corner of the screen.

int fg, bg
The fg and bg fields are the colors to use for the foreground and background of the object. In 8 bpp modes, the numbers are indexes into the system palette (just like colors in an 8 bpp BITMAP). In hicolor or truecolor modes, the colors are in the same format as the results of a call to makecol(). Some objects ignore these parameters.

int key
The key parameter is the code for the key you want bound to the object. The object gets notified when the user presses this shortcut.

int flags
The flags parameter is a bitfield containing various flags that determine the object's state. Possible flags, again from the Allegro documentation, are:

   D_EXIT      - this object should close the dialog when it is clicked
   D_SELECTED  - this object is selected
   D_GOTFOCUS  - this object has got the input focus
   D_GOTMOUSE  - the mouse is currently on top of this object
   D_HIDDEN    - this object is hidden and inactive
   D_DISABLED  - this object is greyed-out and inactive
   D_INTERNAL  - don't use this! It is for internal use by the library...

They're pretty self-explanatory. For example, if an object is disabled, the D_DISABLED flag will be set, and (flags & D_DISABLED) will return non-zero. You can create your own flags, too. There exists a special flag D_USER which you can use for whatever you wish. Any powers of two above this flag (e.g. D_USER << 1, D_USER << 2, etc.) are available for your own use. That is, any powers of two up to 2^31 (which may cause sign-compare problems; I'm not sure). After that, you risk overflowing the number, since it might not be longer than 32 bits on compilers that support Allegro.

int d1, d2 and
void *dp, *dp2, *dp3
The d1, d2, dp, dp2, and dp3 parameters exist for the sole purpose of providing programmer-defined data to the GUI system. You can generally put whatever you want in these fields and act on these values later. Some (in fact, most) GUI objects require specific data to be put into certain fields, so all fields are not always available.

int (*proc)(int, DIALOG *, int) revisited
Finally, the proc pointer. This is a pointer to a "dialog procedure" that gets called whenever something needs to be communicated to the object. It can be named whatever you want, of course, but must satisfy the prototype:

int my_func_name(int, DIALOG *, int);

Not only does this function get called when the GUI system needs to communicate with the object; for our purposes, this function is the object. You see, the first parameter to this function is a message ID, a number which conceptually represents a message, like "initialize yourself," "draw yourself," or "you've been clicked." The GUI system calls this function and sends it a message, which the object (the dialog procedure) then acts upon. If the system says, "draw yourself," the object will draw itself. If it says "clean up; you're expiring," the object deallocates all its memory and cleans up after itself. Of course, the action(s) the object takes can be modified based on the value of the flags parameter in the DIALOG. In fact, the flags had better make a difference! For example, we wouldn't want an object with the D_HIDDEN flag set to draw itself now, would we?

But how does this dialog procedure know what object to modify? Do we have to write a different procedure for every object? No! That's what the second parameter is for (the pointer to DIALOG). Remember, a dialog is really an array of DIALOG structures. Each DIALOG structure contains all the data necessary to encapsulate one object (unless you want to do some really clever advanced GUI work). When the dialog procedure is called, it is passed a pointer to one of the DIALOG structures in the array. The procedure just manipulates this structure by dereferencing that pointer. It's wonderfully abstract, generic, and elegant, and it cleanly allows for some almost object-oriented features. More on that later.

So what's that last parameter to the dialog procedure? When an object receives a message concerning a keypress, this parameter contains the code of the key that was pressed. Objects can use this parameter to differentiate between different keys this way. This parameter is also used to pass around radio button group information in dialogs with radio buttons.

Notice that the dialog procedure returns an integer. What should it return? Here is a list of valid return codes, taken from the Allegro documentation:

   D_O_K        - normal return status
   D_CLOSE      - tells the dialog manager to close the dialog
   D_REDRAW     - tells the dialog manager to redraw the entire dialog
   D_WANTFOCUS  - requests that the input focus be given to this object
   D_USED_CHAR  - the keyboard-handling objects return this if they used the key

The meanings for these values will make more sense as we learn more about the GUI. Basically, they are a way for an object to modify the GUI's behavior when that object is acted upon.

Well, that about sums up the DIALOG structure. Phew! Get to know this structure really well and try to stay on its good side, since familiarity with it is an absolute must for GUI programming in Allegro.

Standard Dialog Controls

Allegro provides a number of prepackaged controls that you can use in your GUI. They're not the prettiest things I've ever seen--I understand that they imitate the look of the Atari ST GUI controls--but they get the job done. They're also a great foundation to work from, since Allegro lets you derive new controls from existing controls. If you're not concerned with looks or just want to create an interface as quickly as possible, these are perfect.

As of this tutorial's writing, Allegro comes with fifteen predefined dialog procedures, or objects (actually, there are sixteen, but the last one is the menu object, which is something of an ugly duckling and will be discussed later). Not all of them are tangible--some are "phantom" objects which only respond to input and never show themselves--but they all have their uses. Here they are, along with their descriptions, again scavenged from the Allegro documentation, with my own modifications:

d_clear_proc
This "draws" itself by clearing the screen to whatever color is given as the bg parameter. It's useful as the first object in a dialog (I can't think of anywhere else you'd want to put it), because whenever the dialog is drawn, the screen will be cleared first. It ignores its x, y, w, and h values.

d_box_proc
d_shadow_box_proc
These draw boxes onto the screen, with or without a "shadow." You can use these to frame things. If you make this object the largest one in a dialog and position everything else within the bounds of this object, it'll look like the border of the dialog's "window." Note that since dialog objects are drawn in the order in which they reside within the array of DIALOGs, this should be near the beginning of the array if you want to create this effect.

d_bitmap_proc
This draws a bitmap onto the screen. The dp field is a pointer to the bitmap you wish to draw.

d_text_proc
d_ctext_proc
These draw text onto the screen, which can be labelled. The dp field should point to the NULL-terminated string you wish to display. d_ctext_proc() centers the string around the x coordinate. Any '&' characters in the string will be replaced with lines underneath the following character, for displaying keyboard shortcuts (as in Microsoft Windows). For example, "&File" will be displayed as "File", but with the "F" underlined. To display a single ampersand, put "&&".

d_button_proc
This is a button object. The dp field points to the caption string, just like d_text_proc(). This object can be selected by clicking on it with the mouse or by pressing its keyboard shortcut. If the D_EXIT flag is set, selecting it will close the dialog; otherwise it will toggle on and off. Like d_text_proc(), ampersands can be used to display the keyboard shortcut of the button. Remember, though, that the actual keyboard shortcut is given in the key field; just underlining a character in the string won't work.

d_check_proc
This is an example of how you can derive objects from other objects. This is basically just a d_button_proc(); the only difference is that it displays itself as a check box. Deriving objects from other objects is discussed later in this tutorial, in Extending the System.

d_radio_proc
This creates a radio button object. A dialog can contain any number of radio button groups: selecting a radio button causes other buttons within the same group to be deselected. The dp field points to the text string (again in the d_text_proc() style), d1 specifies the group number, and d2 is the button style (0 = circle, 1 = square).

d_icon_proc
This is a button, just like d_button_proc except that it doesn't have a caption string: instead, it draws a bitmap. The fg color is used for the dotted line showing focus, and the bg color for the shadow used to fill in the top and left sides of the button when "pressed". d1 is the "push depth", i.e. the number of pixels the icon will be shifted to the right and down when selected (default 2) if there is no "selected" image. d2 is the distance by which the dotted line showing focus is indented (default 2). dp points to a bitmap for the icon, while dp2 and dp3 are the selected and disabled images respectively (optional, may be NULL).

d_keyboard_proc
This is an invisible object for implementing keyboard shortcuts that aren't tied to a particular GUI object (e.g. F1 for Help). You can put an ASCII code in the key field of the dialog object (a character such as 'a' to respond to a simple keypress, or a number 1-26 to respond to a control key a-z), or you can put a keyboard scancode in the d1 and/or d2 fields. When one of these keys is pressed, the object will call the function pointed to by dp. This should follow the form:

   int keyboard_callback();

The returned integer will be passed back to the dialog player, so it can return a known value (D_O_K, D_REDRAW, and so on).

d_edit_proc
This is an editable text box (the dp field points to the string). When it has the input focus (obtained by clicking on it with the mouse), text can be typed into this object. The d1 field specifies the maximum number of characters that it will accept, and d2 is the text cursor position within the string.

d_list_proc
This creates a list box object, which will allow the user to scroll through a list of items and to select one by clicking or by using the arrow keys. If the D_EXIT flag is set, double clicking on a list item will close the dialog. The index of the selected item is held in the d1 field, so you can check which item is currently selected by looking at this value. d2 is used to store how far it has scrolled through the list. The dp field points to a callback function which you create; it will be called to obtain information about the contents of the list. This should follow the form:

   char *foobar(int index, int *list_size);

If index is zero or positive, the function should return a pointer to the string which is to be displayed at position index in the list. If index is negative, it should return NULL and list_size should be set to the number of items in the list.

To allow multiple selections in your listbox, point the dp2 field to an array of byte flags indicating the selection state of each list item (zero for unselected entries, non-zero otherwise). This table must be at least as big as the number of objects in the list, or you'll be riding the highway to SIGSEV!

d_textbox_proc
This is a multiline text box object. The dp field points to the text which is to be displayed in the box. If the text is long, there will be a vertical scrollbar on the right hand side of the object which can be used to scroll through the text. The default is to print the text with word wrapping, but if the D_SELECTED flag is set, the text will be printed with character wrapping. The d1 field is used internally to store the number of lines of text, and d2 is used to store how far it has scrolled through the text.

d_slider_proc
Lastly, this is a slider object. This object holds a value in d2, in the range from 0 to d1. It will display as a vertical slider if h is greater than or equal to w, otherwise it will display as a horizontal slider. The dp field can contain an optional bitmap to use for the slider handle, and dp2 can contain an optional callback function, which is called each time d2 changes. The callback function should have the following prototype:

   int function(void *dp3, int d2);

So, point dp3 to something that can identify the slider, if you need to be able to tell which slider you're working on. The d2 parameter will contain the slider's value. The d_slider_proc object will return the value of the callback function.

These predefined objects are enough to create an interface for almost any program. To use one, simply point a DIALOG's proc pointer at it. For example, to use a slider, you'd do something like

   my_dialog[foo].proc = d_slider_proc;

Experimentation is probably the best way to learn how to use these objects. In the next section, we'll create a sample dialog using some of them.

Creating and Running a Dialog

Once you know what you want your dialog to do, you're ready to build it. Building a dialog is easiest if you declare and define it statically; dynamically creating dialogs is a very powerful thing, but it's more difficult.

Declaring a dialog is as easy as declaring and initializing an array of structures. In case you don't know how to do that, here's a quick tour. Just as you can initialize a single structure like this:

   typedef struct {
      int x;
      int y;
   } POINT;

   POINT midpoint = { 30, 50 };

You can also create an array of structures like this:

   POINT five_midpoints[] = {
      { 30, 50 },
      { 62, 19 },
      { 11, 15 },
      { 38, 30 },
      { 27, 82 }
   }

In this example, five_midpoints[0].x == 30, five_midpoints[2].y == 15, and so on. It's easy.

Back to the tutorial. To declare a dialog, first figure out what it should look like; sketching the dialog out on paper is sometimes helpful here. Make sure you know what dialog controls you will be using, and where they will go. Then declare your dialog using the syntax discussed above.

But can we have an example, please? Sure. Consider the ubiquitous RGB color builder. I'm sure you've seen one. The user changes three numbers, representing red, green, and blue, and these are mixed together to make a color, which is usually displayed in a box. Let's create one.

Designing the Color Picker
First, decide what the dialog will look like. For this example, I've chosen a simple look: three vertical sliders and one filled box should work. Fortunately, d_slider_proc and d_box_proc fit the job nicely, so we won't need to create anything new. Here's the declaration for the dialog (special thanks goes to Shawn Hargreaves for writing Allegro example 30, on which this example is based):

   DIALOG color_builder[] =
   {
      /* (dialog proc)
         (x)   (y)   (w)   (h)   (fg)  (bg)
         (key) (flags)(d1) (d2)
               (dp)  (dp2) (dp3) */

      { d_clear_proc,
           0,    0,    0,    0,    255,    0,
           0,    0,    0,    0,
              NULL, NULL, NULL },

      { d_box_proc,
           0,    0,  100,  100,    0,    254,
           0,    0,    0,    0,
              NULL, NULL, NULL },

      { d_slider_proc,
          10,  110,   16,   64,    1,      0,
           0,    0,   63,    0,
              NULL, update_color, NULL },

      { d_slider_proc,
          42,  110,   16,   64,    2,      0,
           0,    0,   63,    0,
              NULL, update_color, NULL },

      { d_slider_proc,
          74,  110,   16,   64,    4,      0,
           0,    0,   63,    0,
              NULL, update_color, NULL },

      { NULL,
           0,    0,    0,    0,    0,      0,
           0,    0,    0,    0,
              NULL, NULL, NULL }
   };
Sorry about the ugliness of the declarations. They're easier to read if they're written one line per control, but that doesn't work well with the format of this web site! For an easier-to-read version, download the source code and follow along.

Anyway, what does this say? Well, each control is given a dialog procedure to use, except the last one, which is NULL (remember, that terminates the dialog). As you can see, we have our "clear screen" control, our color box, and three sliders. They are given positions and sizes in their x, y, w, and h parameters, and then foreground and background colors in the fg and bg fields. . . you get the idea. The controls are given starting values.

The Mysterious Callback
You may have noticed that the sliders point to a mysterious procedure called update_color for their callback. What does this do? Take a look:

   /* slider ID's within the dialog */
   #define S_RED    2
   #define S_GREEN  3
   #define S_BLUE   4


   /* callback for the sliders (the parameters are
    * ignored, since we already know what controls
    * to look at for data)
    */
   int update_color(void *dp3, int d2)
   {
      RGB col;

      col.r = color_builder[S_RED].d2;
      col.g = color_builder[S_GREEN].d2;
      col.b = color_builder[S_BLUE].d2;

      vsync();

      set_color(254, &col);

      return 0;
   }
Remember that the callback is called every time one of the sliders moves. When that happens, this routine reads the value of each slider and changes color 254 to match them. Not-so-coincidentally, color 254 is the background color of the box in our dialog. So, when a slider moves, the color of the box automatically changes.

Running the Color Builder
Well, our dialog is complete, but we don't have a program do drive it. Here's a simple main() function:

   int main()
   {
      /* initialize */
      allegro_init();
      install_keyboard();
      install_mouse();
      install_timer();
      set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0);
      set_palette(desktop_palette);

      /* initialize the color box */
      update_color(NULL, 0);

      /* run the dialog */
      do_dialog(color_builder, -1);

      return 0;
   }
As you can see, there's not much we need to do beforehand. We just initialize the library, set up our input devices, set the graphics mode and the palette, and go. We call update_color() before running the dialog to initialize color 254 to black, since the default color is incorrect After that, the call to do_dialog() handles everything else!

If you'd like the source code to this program, you can download color.c.

Voila! A working dialog! Of course, this program is pretty useless on its own. It's only meant to demonstrate one way of thinking about the GUI and using the routines. Enhancing it is up to you.

Earlier, we noted that there are sixteen prepackaged controls that come shipped with Allegro. However, we only covered fifteen of them. In the next section, we'll introduce the menu object and discuss how to use it.

Next: Building and Using Menus

 

About this site

The Allegro GUI Clinic is © 1998 Revin Guillen