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 Menus
 
  GUI Clinic Home
 
  How do I Put Menus into My Program? | The MENU Structure | Running a Menu  
 

Previous: Building and Using Dialogs

How do I Put Menus into My Program?

So far, we've covered all of Allegro's standard dialog controls, save one: the menu control. Conceptually, menus are a lot like the standard controls: fill out a DIALOG structure to represent the menu and manipulate its data fields to modify its behavior. Easy. However, there is a fair amount of power hidden behind that statement, more so than with most of the GUI objects. That's why this control has its own section.

The menu control is based on two parts: the dialog procedure and the MENU structure. As for the first part, Allegro's documentation again contains an entry for this object (you may be getting the idea that it would be a Good Thing to read the documentation, and it definitely is; Shawn, et al put a lot of work into it):

d_menu_proc
This object is a menu bar which will drop down child menus when it is clicked or if an alt+key corresponding to one of the shortcuts in the menu is pressed. It ignores a lot of the fields in the dialog structure, like its color and size. The dp field points to an array of menu structures: see below for more information. The top level menu will be displayed as a horizontal bar, but when child menus drop down from it they will be in the normal vertical format used by do_menu() (discussed later). When a menu item is selected, the return value from the menu callback function is passed back to the dialog manager, so your callbacks should return D_O_K, D_REDRAW, or D_CLOSE.

So what about the second part of the menu control, the MENU structure? Read on.

The MENU Structure

The second part of the menu control is an array of MENU structures, much the same way a dialog is made up of an array of DIALOG structures. A d_menu_proc object's dp field will point to this array. The array is terminated by an entry with a NULL text pointer. Here's how this structure is, well, structured:

   typedef struct MENU
   {
      char *text;
      int (*proc)();
      struct MENU *child;
      int flags;
      void *dp;
   } MENU;

Let's discuss this structure, shall we? Most of the following is paraphrased from the documentation.

char *text
This points to the menu's text string. As with many of the other GUI objects, ampersands are interpreted as meaning the next character should be underlined. "&File" would be shown as "File", but with the "F" underlined. If this string contains a tab character (\t), the text following it will be right-justified in the menu, which is handy for displaying the keyboard shortcut (e.g. "&New\tCtrl-N"). To display a menu separator, point to a zero-length string (which is different from making the pointer NULL).

int (*proc)()
This is a pointer to a callback function that will be called when the menu item is clicked. It may be NULL, in which case the system will ignore it. It should satisfy the prototype (call it what you like, of course):

   int menu_callback();

The return value will be given back to the dialog manager if the menu was part of a dialog; if the menu was processed by calling do_menu(), it is ignored.

struct MENU *child
This points to another menu, which will become a child menu (to be raised when its parent is clicked). This may be NULL.

int flags
This can hold exactly the same flags as the flags field in a standard dialog object. Only a few actually affect the menu, however. If the D_DISABLED flag is set, then the menu item will be grayed out and inaccessible. If the D_SELECTED flag is set, a check mark will be displayed in front of the item. This is useful for creating a menu item that can toggle on or off. The check marks have a tendency to overlap with the menu text, so if you use them, add one or two spaces to the beginning of each menu item's text string.

void *dp
This points to any data you need for yourself. Allegro doesn't use it.

Creating and Running a Menu

If you can visualize a menu and initialize an array of structures, you can create an Allegro menu object. It's one of the easier things you can do with the GUI (it's certainly much easier than designing menuing systems by hand for one of the big-time GUI systems). You can run an Allegro menu in two ways: by using do_dialog() and activating a menu, or by calling do_menu() directly. We'll cover both ways here.

Let's build an example program. To simplify things, our program will consist of only menuing code, with only what is necessary to support it. That should keep it small. We'll build a program that uses menu selections to change the color of some on-screen text, sort of like the program we built in the section on dialogs.

Designing the Menu
To start, let's decide what should be in our program. We want to show an example of just about every menu function, so let's have a two-member top-level menu. One of those menus will contain a child menu, and one of the menu items will have a keyboard shortcut. Let's also have a popup menu.

The declaration for such a menuing system follows:

   MENU file_menu[] =
   {
      { "&Disabled",     NULL,    NULL, D_DISABLED, NULL },
      { "",              NULL,    NULL, 0,          NULL },
      { "E&xit\tCtrl-X", quitter, NULL, 0,          NULL },
      { NULL,            NULL,    NULL, 0,          NULL }
   };

   MENU child[] =
   {
      { "&Dark",   dark_red,   NULL, 0, NULL },
      { "&Bright", bright_red, NULL, 0, NULL },
      { NULL,      NULL,       NULL, 0, NULL },
   };

   MENU text_menu[] =
   {
      { "&Red",   NULL,       child, 0, NULL },
      { "&Green", text_green, NULL,  0, NULL },
      { "&Blue",  text_blue,  NULL,  0, NULL },
      { NULL,     NULL,       NULL,  0, NULL }
   };

   MENU main_menu[] =
   {
      { "&File", NULL, file_menu, 0, NULL },
      { "&Text", NULL, text_menu, 0, NULL },
      { NULL,    NULL, NULL,      0, NULL }
   };

Note the relationship between the menus: The top-level menu is main_menu. It has two children, file_menu and text_menu. The "Red" item in text_menu has its own child. It could go deeper, but it shows how things work.

Powering the Menu
A menuing system won't work unless you give it something to do when the user clicks on menu items. The menu items point to quite a few little callbacks. They're quite simple, really. Here they are:

   void change_color(int r, int g, int b)
   {
      RGB col;

      col.r = r;
      col.g = g;
      col.b = b;

      vsync();

      set_color(254, &col);
   }

   int quitter()
   {
      return D_CLOSE;
   }

   int dark_red()
   {
      change_color(32, 0, 0);
      return D_O_K;
   }

   int bright_red()
   {
      change_color(63, 32, 32);
      return D_O_K;
   }

   int text_green()
   {
      change_color(0, 63, 0);
      return D_O_K;
   }

   int text_blue()
   {
      change_color(0, 0, 63);
      return D_O_K;
   }

   int text_purple()
   {
      change_color(48, 0, 48);
      return D_O_K;
   }

   int text_orange()
   {
      change_color(63, 32, 0);
      return D_O_K;
   }

   int text_black()
   {
      change_color(0, 0, 0);
      return D_O_K;
   }

So basically, when the user clicks on a menu item, the GUI calls the appropriate menu callback, which in turn calls change_color(), which changes the color of the text in the main dialog (which we haven't gotten to yet). Easy.

As a side note, you may have noticed that one source of ugliness with GUI code is that the menu items all have their own little functions that clutter up the source. Oh, well...

Smell something fishy? I do. There are three extra callbacks in the above list! Where'd they come from? You didn't think we'd finish without demonstrating popup menus, did you?

Popping Up
Implementing a system of popup menus is easy. First, you must create the menu. This works exactly the same way for popup menus as it does regular menus, so you already know enough to get past this step. The next thing is to create a way for the program to call do_menu(), which displays popup menus. For this example, we'll have the popup menu appear in response to a keypress.

So exactly what does do_menu() do? As usual, the documentation can clue us in:

int do_menu(MENU *menu, int x, int y)
Displays and animates a popup menu at the specified screen coordinates (these will be adjusted if the menu does not entirely fit on the screen). Returns the index of the menu item that was selected, or -1 if the menu was cancelled. Note that the return value cannot indicate selection from child menus, so you will have to use the callback functions if you want multi-level menus.

Note that you can either check the return value from this function and take action based on the user's menu choice, or simply use menu callbacks (just like you would with menus inside dialogs). Or, you can use some sort of clever combination of both, for some sort of hybrid multi-callback checking system. I'll leave that up to you. For our example, we'll stick to using callbacks.

Here is our popup menu definition, as well as its "driver" function:

   MENU popup_menu[] =
   {
      { "&Purple", text_purple, NULL, 0, NULL },
      { "&Orange", text_orange, NULL, 0, NULL },
      { "&Black",  text_black,  NULL, 0, NULL },
      { NULL,      NULL,        NULL, 0, NULL }
   };


   /* popup menu function */

   int run_popup()
   {
      do_menu(popup_menu, mouse_x, mouse_y);
      return D_O_K;
   }

Nothing new here, really, except for the call to do_menu(). It just displays our popup menu at the same position as the mouse.

Driving Miss Menu
Well, that's everything as far as the menus are concerned. All that's left is to declare our main dialog and implement a main() function that acts as a driver for our dialog. Without further ado, here's the rest of the program:

   #define ctrl(x)      (x - 'a' + 1)

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

      { d_clear_proc,    0,     0,   640, 480,
                         0,     0,   0,
                         0,     0,   0,   NULL },

      { d_menu_proc,     0,     0,   0,   0,
                         0,     0,   0,
                         0,     0,   0,   main_menu },

      { d_ctext_proc,    320,   240, 300, 20,
                         254,   0,   0,
                         0,     0,   0,   "Some text" },

      { d_keyboard_proc, 0,     0,   0,   0,
                         0,     0,   ctrl('x'),
                         0,     0,   0,   quitter },

      { d_keyboard_proc, 0,     0,   0,   0,
                         0,     0,   ctrl('m'),
                         0,     0,   0,   run_popup },

      { NULL,            0,     0,   0,   0,
                         0,     0,   0,
                         0,     0,   0,   NULL }
   };


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

      /* initialize the text color */
      change_color(0, 0, 0);

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

      return 0;
   }

As with the dialog example, downloading the source code will make things easier to read, since the dialog is declared one line per object.

Notice that there are a couple of d_keyboard_proc objects in the main dialog. They handle the keypresses for our program. If the user presses Ctrl-M, the popup menu is displayed. If the user presses Ctrl-X, the GUI calls the quitter() function, which is also called when the user clicks File | Exit. You see, since the d_keyboard_proc objects use the exact same kind of callbacks as the d_menu_proc objects, they can share functions. This makes it very easy to provide keyboard shortcuts for menu items.

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

By now, you should have enough knowledge to go out and create your own GUI-based programs. However, you'll quickly learn that the GUI controls don't always do exactly what you want them to do. And even when they do, you'll probably find yourself dissatisfied with the rather dated look of the Atari ST GUI. Fear not, brave coder! The true power of Allegro's GUI comes from the fact that you can modify it without hacking into its internals. The GUI subsystem has some object-oriented-esque(!) features, which allow you to enhance it by deriving new controls from pre-existing ones, and in a fairly rapid manner.

We'll talk about that and more in the next few sections. By the time we're through, you'll even be able to create your own GUI objects from scratch! Stay tuned!

But first, let's learn about some simple ways to change the GUI system's behavior...

Next: Modifying GUI Behavior

 

About this site

The Allegro GUI Clinic is © 1998 Revin Guillen