|
|
|
| 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?
Here's what a DIALOG looks like, from the Allegro documentation: typedef struct DIALOG
What do all these fields do? Here's the scoop: int (*proc)(int, DIALOG *, int)
int x, y, w, h
int fg, bg
int key
int flags
D_EXIT
- this object should close the dialog when it is clicked
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
int (*proc)(int, DIALOG *, int) revisited
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
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.
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
d_box_proc
d_bitmap_proc
d_text_proc
d_button_proc
d_check_proc
d_radio_proc
d_icon_proc
d_keyboard_proc
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
d_list_proc
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
d_slider_proc
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.
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 {
You can also create an array of structures like this: POINT five_midpoints[] = {
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
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 /* 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
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 |