[net.micro.amiga] Menus/Requesters Tutorial

crunch@well.UUCP (John Draper) (12/31/85)

                     MENUS, REQUESTORS, AND GADGETS
                                  BY
                            John T. Draper
                 A service from the Programmers Network
                       on the WELL in Sausalito.

  Permission to post this on other networks is granted provided the
source of this information is included.   The programmers network is
a non-profit network exchange of programming information.   For more
information,  mail your requests to:

WELL: crunch
BIX:  crunch
USENET: ihnp4!ptsfa!well!crunch
DELPHI: crunch


       ========================= MENUS =========================

   Menus are activated by pressing the RIGHT mouse button, causing 
little windows to open up along the top of the screen.   When the right
mouse button is pressed,  a row of words are displayed at the top.  Each
of these words are the name of the menu. The row of menu names along
the top are called a MENU STRIP.   

   When you point to a name, press the mouse button;  a little window
or rectangular square is displayed showing the names of the menu ITEMS.
Notice that, as a menu name is selected,  the name is inverted in a dark
rectangle.   The width of the rectangle ( in the menu strip),  is
the MENU NAME WIDTH.   Just below the menu strip is a box.   Contained
within the box are names for the menu ITEMS.   The width of the box I 
will call ITEM BOX WIDTH.   The menu name width and the item box width
have to be chosen properly in order for the menus to work and they are
related in a specific way.   If they are set up improperly,  they 
won't work.

   It is possible to write procedures that automatically set up the
proper widths,  but that is beyond the scope of this article.   Such
a procedure might take up an  unnecessary amount of memory.

   Amiga programs use two types of memory: that allocated by initialized
and declared variables and structures; and that allocated by the memory
manager supplied by intuition.   This program can easily be modified to
allocate menus outside of the program memory area, thus making more room
for large programs.

   This example attempts to use the "best of both worlds"  by using a
short piece of code to initialize and link the Menu items, and yet
having the flexability offered by initializing the structures at compile
time.

   There are advantages and dis-advantages to each method.   I will attempt
to explain them here.

METHOD #1 - Initializing the structures at declaration.   For example:

struct Menu req_menu = {
   NL,                             /* NO Next menu           */
   FIL_WIDTH + ITEM_WIDTH,         /* LeftEdge               */
   0, REQ_WIDTH, 10,               /* TopEdge, Width, Height */
   MENUENABLED,                    /* Flags                  */
   "Requestors",                   /* Menu name              */
   req_items                       /* Pointer to items list  */
};

   This code initializes and declares the structure at compile time.
This is nice for quick programs,  or programs that are small.   BUT!!-
here's the clincher!!   If your program has a lot if structures to
initialize,   IT INCREASES COMPILE TIMES. It also clutters up the program, 
because you have to read through pages and pages of these structure 
declarations to change your menus.   You cannot easily add or  remove 
items without going to several places in your code and changing it.   
It also takes up more program space.   Perhaps it doesn't matter on the 
Amiga,  but on the Macintosh,  there is a limited size your code module 
can have.   In the example program that comes with this article,  I use 
method #1 for ONLY the menus and NOT the menu items, because there is 
only a limited number of menus,  due to the size of the top strip.  
(menu bars for those Mac freaks).

METHOD #2 - Calling a function to initialize an array of structures.
This is what I do in this example.   In this example,   I am only
using text items- but only a little bit of modification will allow
for IMAGE items.   The sprite editor,  soon to be here,  contains these
types of menus.   Usually,  only ONE intuitext and MenuItem structure
is initialized and are then copied into an array of them.   This array
can be allocated using the memory manager, then released when finished.



DATA STRUCTURES USED IN MENUS
-----------------------------
  
  If you plan on having menus in your program,  you have to prepare two
basic types of structures,  and initialze their values.   These structures
have to be initialized properly in order for your program to work.  In
my first experience of using menus,  I spent over a week figuring them out.
The intuition manual wasn't too clear on how to set them up.   There
was an omission of an important field on page 6-15 of the intuition
manual.   The field is "SelectFill", which is between ItemFill and
Command.   You should mark this in your manual so you won't be confused.


MENU STRUCTURE
--------------

   A "Menu" structure describes the characteristics of the "thingey" that's
at the TOP of the screen.   It describes LeftEdge,  TopEdge,  Width,
and Height.   This is NOT, I repeat NOT, the width and height of the
little window that pops out when that menu is selected.   For instance,
TopEdge and Height are IGNORED by Intuition.   Leftedge describes the
leftmost position RELATIVE TO THE SCREEN, where the black
select box will be rendered.   Eight pixels to the RIGHT of this box is
where the first character of the NAME of the menu will be rendered.
LeftEdge also cannot be a negative number.   The Leftedge of the NEXT
menu will equal the Leftedge + Width of the previous menu.

   Menu flags are provided to specify if the menu is currently enabled
or disabled (MENUENABLED),  or whether the Menus items are currently
displayed to the user.

   There is a pointer to the Menu name which will be displayed on the
top menu strip and a pointer to the FIRST MenuItem structure which
describes each ITEM the menu is to have.


ITEMS STRUCTURE
---------------

   Each menu usually has more than one ITEM.   An ITEM name is displayed
inside an ITEM BOX.   An ITEM BOX is the little box you see pop up if you
point and press the RIGHT mouse button at a specific menu name along the
top MENU STRIP.  MenuItem structures contain information that describes
the ITEM BOX and the item name.  

   Other information included in the MenuItem structure include a pointer
to the NEXT item in the menu.   Each item has its own set of flags that
can be used to tell Intuition to put a checkmark on the item.   You
can check the MenuItem whether it's checked by checking the CHECKED flag.
You also have to have the CHECKIT flag set.

   If your menu items are text names,  then you set the ITEMTEXT flag.
You can also specify an optional command key activation.   Setting
the COMMSEQ key, then putting the ascii value of the key into the
"Command" field, will cause that item to be selected when that key
and the RIGHT AMIGA key are pressed at the same time.

   If you want the item to be enabled and activated,  as most do,  then
set the ITEMENABLED flag.

   There are flags to describe how you want the highlighting to be done
when the item is selected.  HIGHCOMP will draw the selection box in the
Inverse mode,  while HIGHBOX will draw a box around the selection box.
I tried BOTH HIGHCOMP and HIGHBOX,  but nothing appeared to be selected.

   If you are using Images in your menu,  set the HIGHIMAGE flag.   You 
will usually want to set the HIGHBOX flag,  because it will stand out
better.   If you set the HIGHCOMP instead of the HIGHBOX,  the image
will be inverted;  it might not look right, especially if you are using
the Images to display color pattettes.

   An easy routine called NewMenu has been included which automatically
fills in the menuItem structure.   A description of it is listed below
in the "checklist" for setting up the menus.   This allows the use of
the Memory manager to allocate a lot of menu-item structures.


HANDLING MENU ACTIONS
---------------------

   Now that we have created the menus,  and have them popping out of the 
top of the screen,  we have to USE THEM.   Somehow,  we have to set up
our program so that certain actions can happen when the menu is activated
by the user.   This happens when the user lets go of the mouse button
after an item is selected.   Below is a "Checklist" which will enable you
to set up your program accurately.

____1.  In the NewWindow structure,  make sure the MENUPICK flag is specified.
        If not,  then you will never get any Menu related events from Intuition.
        The Intuition manual doesn't make this clear.

____2.  After setting the IDCMP flag MENUPICK in the NewWindow function,  you
        have to make sure ALL the menu structures,  MenuItem structures,
        and Intuitext or Image structures associated with MenuItem structures
        are initialized properly.   After calling OpenWindow,  you must call
        your Menu initialization procedure.   In this example,  I call it
        NewMenu.   NewMenu essentually initializes the MenuItem,  structures
        related to your menu.   It also fills in the pointer to the IntuiText
        structure if the menu items are text,  or Image structure if the
        menu contains ICONS or images.   You pass it the following:
      
        o - Address of the Menu structure.  Ex:   &fmenu,  if the menu
            structure is called fmenu.

        o - Address of an array of menu item name pointers.  These pointers
            are put into the Intuitext items ONLY if the ITEMTEXT flag is set.
            if not, then this pointer is a pointer to a bitmap image.

        o - Pointer to the first MenuItem structure in a linked list of other
            structures.   This routine links them for you:  just tell
            it where the FIRST MenuItem structure is,  and arrange for the
            other structures to be right next to it.  Just declare an array
            of MenuItem structures  (See the examples).   There are as many
            MenuItem structures as there are Menu items.

        o - Pointer to the First intuitext structure in an array of them.  
            There are as many IntuiText structures as there are MenuItem
            structures.  If the ITEMTEXT flag is NOT set,  then we assume
            that we are pointing to an Image structure.

        o - The Number of items this menu is to have.

        o - The Widths of each of the ITEM BOX WIDTHS.  Remember, the Item
            box width is the pop up window width which contains the text item
            names or the Images.

        o - MenuItem Flags.  All MenuItem structures will be filled with these
            flags.   If you need different flags set,  you should do them
            individually after calling this (these) functions.


____3.  If any of the MenuItems need DIFFERENT flag settings.  Do them NOW.

____4.  Attach the menustrup to the window.   Call SetMenuStrip (See Example)
        to tell Intuition to "Recognize" these new structures we just set up.

____5.  Now, modify the Event loop to "Recognize" menu actions by adding
        a new "case" statement as shown below.   MENUPICK is the new case
        we need to add.

   for (;;)
   {
 
      if ((message = (struct IntuiMessage *)GetMsg(w->UserPort)) == 0L)  {
          Wait(1L<<w->UserPort->mp_SigBit);
          continue;
      }
        class = message->Class;
        code = message->Code;
        ReplyMsg(message);
        switch (class) {
 
           case CLOSEWINDOW : close_things();
                              exit(0);
                              break;
 
           case MENUPICK    : if (MENUNUM(code) != MENUNULL)
                                domenu(MENUNUM(code), ITEMNUM(code),
                                       SUBNUM(code));
                              break;
 
           case MOUSEBUTTONS: break;
        }   /* Case */
   }  /* for */

____6.  Now we define "domenu" as shown above.   We pass it three arguments,
        and it is responsible for selecting the proper thing to do.   These
        arguments are:
    
        o - Menu number....Specifies which menu.   Menu number 0 is the
            left most menu on the screen.   The next one is menu number 1.

        o - Item number....Specifies the menu ITEM number from 0 to n.  

        o - Submenu item...Usually equals to 32 if there are none.

        See the example program included in this tutorial.   Normally,
        your code might look like the example, but it usually depends
        on your personal taste.  I used the "case" example because it
        is easy to read.   A true C jock might hammer it into a more
        efficient form.

REQUESTERS - The easy kind
--------------------------

   Requesters turned out to be a real pain in the ....   The Intuition
manual really lacks good documentation,  and because of the kindness of
Jim, and Dave Lucas at Amiga R&D,  I was able to sucessfully create a
custom requester.   

   Even simple AutoRequest turned out to be a pain because of the way
the arguments are passed to AutoRequest.   Because the Lattice C thinks
of "int's" as 32 bits,  and Manx C uses "int's" as 16 bits,   It wasn't
possible to pass the arguments properly without the "L" after the literal
digit.   After much fussing around with the borders,  and gadget rectangles
I was able to make a few sample AutoRequests.   The thing you have to
remember when using AutoRequests- If you're using a C compiler using
16 bit ints- is that the size and position arguments are 32 bits.

  val = AutoRequest(w, &AutoText, &TRUEtext, &FALSEtext, 0L, 0L, 319L, 60L );
  if (val)
       printf("TRUE\n");
   else
       printf("FALSE\n");
   

   Above is a sample of how to pass numeric arguments to AutoRequest.
Note the "L" after the digits.  AutoText, TRUEtext, and FALSEtext are
IntuiText structures.   If you are using Lattice C,  remove the "L's"
shown above.

   This function essentually "steals" control from your main "Message
loop",  and handles messages ONLY generated by the requester.   You
COULD use the main program event loop and add an extra case statement
switching on REQCLEAR messages.



BUILDING YOUR OWN REQUESTERS
----------------------------

   In order to build your own requester,   you will need to declare one or
more "Requester" structures:  at least ONE gadget AND border per structure.   
Naturally, you will probably want your own custom Image or Text describing 
to the user what the requester is to do.

Example:
struct Requester req;     /* Custom Requester structure */

   After all the structures are declared,   you will have to call
InitRequester, passing it a pointer to your "Requester" structure.
NOTE:  In the Intuition manual,  the example showing the declaration
and initialization of the Requester structure really isn't necessary.
InitRequester will zap all of those fields anyway.   After talking to
Amiga folks,  they admitted that it really isn't appropriate to 
pre-initialize the Requester structure.

Example:
   InitRequester(&req);          /* Init the requestor              */


   You DON'T HAVE TO Initialize the "Requester" structure,  because
Intuition does it FOR you.   InitRequester will ZERO OUT all the fields
in the Requester structure,  and also do other things.

   AFTER calling InitRequester,  you then have to initialize the specific
fields in the Requester structure as shown below.   This is usually done
AFTER creating the window, and BEFORE entering the event loop.

   req.LeftEdge  = 20;
   req.TopEdge   = 20;
   req.Width     = 250;
   req.Height    = 80;
   req.ReqGadget = &ongad;        /* First gadget in Requester */
   req.ReqText   = &text;         /* Text for requester */
   req.BackFill  = 1;             /* BackGnd color to window */
   req.Flags     = 0;
   req.ReqBorder = &out_border;  /* OutSide bord - Must have at least one */
 
   &ongad = Pointer to the first gadget in the requester.

   &text  = Pointer to Intuitext text structure and renders text into
            the requester.

   &out_border = This is the outside border to the structure.   You MUST 
            have at least ONE border structure.


USING CUSTOM REQUESTERS
-----------------------

   This was the part where I had the most problems.   I had to get help
from Amiga to figure this out.   

   Lets suppose the programmer wants to bring up a requester when a menu
choice is made.   According to the Intuition manual,   you call
Request()  function,  and the requester is supposed to come up on the
sceen.   It does.   Just fine.    HOWEVER-  what then??!?  

   You are then supposed to go into your own "wait" loop,  getting
messages from the IDCMP.   You are waiting for Intuition to send you
a REQCLEAR message class.   This means your NewWindow structure BETTER
have the REQCLEAR flag set,  otherwise you have no way to tell when
the user presses the gadget in your request.

   After you get the REQCLEAR message,  you will need to snatch a copy
if the IAddress field of the IntuiMessage structure BEFORE you reply
to the message.    The IAddress field contains the Address of the
gadget structure acted upon by the user.   You can use the GadgetID
field as your own personal method of identifying the gadget and act on it
accordingly.

   When you have finished processing this action,   you might think that
the appropriate thing to do would be to call EndGadget.   But as you
can see,  I DON'T.  I set the ENDGADGET flag in the Activation
field of the gadget.  If this flag is set,  you DON'T have to call
EndGadget; and if you do,  your system goes away.   Intuition closes
the Requester for you when THAT gadget is selected.    You can have
MORE THAN ONE gadget with the ENDGADGET set.   If you DON'T set the
ENDGADGET,  then it's safe to call "EndRequest".   A good way to tell
when Intuition trashes your requester is to examine the FirstRequest
field in the Window record.   The "Request" function stuffs the
FirstRequest field with a pointer to the Requester structure.   EndRequest
removes this pointer and sets it to NULL.    It might be safe to
use this procedure while calling EndRequest.

    if (w->FirstRequest != NULL)
        EndRequest (&req, w);


PROCEDURE FOR MAKING AND USING CUSTOM REQUESTERS
------------------------------------------------

o - Sketch out the requester in rough draft form.   Decide how many
    gadgets the requester is to have.

o - Declare and initialize all the Gadget,  Intuitext,   image or
    other structures needed by this requester.   Remember to set the
    REQGADGET flag in the GadgetType fields of ALL the gadgets.   Also,
    set the ENDGADGET flag in the gadget's Activation field if you want
    the gadget to automatically go away after processing the REQCLEAR
    message.   This can take several pages of structure definitions.
    
    If your Requester Gadget uses an Image instead of text,  you have
    to set BOTH  GADGIMAGE | GADGHIMAGE flags in the "Flags" field
    and then leave pointers to their "Image" structure like the
    following example:

o - If you want to process a gadget within a requester,  but leave the
    requester up on the screen,  that gadget MUST have the 
    GADGIMMEDIATE flag set as shown below.   This causes Intuition to
    send you a GADGETDOWN message class.   Remember to SET the 
    GADGETDOWN flag in the NewWindow structure,  otherwise you WON'T
    get the Message back from Intuition.
    
    
struct Gadget face_gad = {
  NULL,
  30, 18,                              /* LeftEdge, TopEdge     */
  32, 20,                              /* Width,  Height        */
  GADGHIMAGE | GADGIMAGE | GADGHIMAGE, /* Flag                  */
  RELVERIFY | GADGIMMEDIATE,           /* Activation            */
  BOOLGADGET | REQGADGET,              /* GadgetType            */
  (APTR)&smil_face,                    /* GadgetRender - Border */
  (APTR)&tong_face,                    /* SelectRender          */
  &gag_me,                             /* "Gag me"              */
  NL, NL, NL, NL                       /* Mut Excl, Spec Info,  */
};

    The &smil_face is a pointer to an "Image" structure.  See listing
    included in this tutorial.

o - If you are building your own custom requesters,  you will need to
    set the IDCMP flag REQCLEAR in your NewWindow structure as long as
    you expect Intuition to clear your requester for you by setting the
    ENDGADGET flag in the gadget's Activation field,  and expect to 
    get the REQCLEAR message from IDCMP.   If you DON'T set this flag, 
    then YOU must call "EndRequest" to remove your requester.

o - In your initialization code somewhere before your message loop,   you
    will need to call InitRequester.   Pass it the pointer to your
    requester structure.   This clears out all the fields of your
    requester and does other internal initializations.   Example:

   InitRequester(&req);          /* Init the requestor              */

    You need to do this for Every requester you expect to have.

o - Initialize the fields in your requester array.   It should always
    be done AFTER calling InitRequester because it zeroes out all the
    elements in the structure.   Example:

/* Init the fields in the Requester structure */
 
   req.LeftEdge  = 20;
   req.TopEdge   = 20;
   req.Width     = 250;
   req.Height    = 80;
   req.ReqGadget = &ongad;        /* First gadget */
   req.ReqText   = &text;         /* Text for requester */
   req.BackFill  = 1;             /* BackGnd color to window */
   req.Flags     = 0;
   req.ReqBorder = &out_border;  /* Must have at least one */

 
CALLING UP THE REQUESTER
------------------------

o - Call "Request", pass it a pointer to your request structure and a
    pointer to your window.   This brings up the requester box in your
    window.   The code listed below checks the returned value.  If
    the value is 1,  then it was sucessful.  "do_req(&req)" does the next
    thing explained below.
    

            case   TEX_REQS: if ((val = Request(&req, w)) == 1) {
                               do_req(&req);
                             }
                             break;


o - Go into a Wait and GetMsg loop "listening" for the REQCLEAR message
    class.   Before "Replying" to the message,  store the IAddress field
    and the class of the IntuiMessage into local variables,  then Reply
    to the Message.   If the "class" is equal to REQCLEAR,  then use the
    IAddress value (which points to the gadget that user pressed) to
    identify the gadget, and do the appropriate action.    You DON'T
    have to call EndRequest after handling gadget action IF you
    set the ENDGADGET flag in the gadget structure that was explained
    above.  If you have to process any gadgets,  you have to check for
    GADGETDOWN message class and process it.    The mes->IAddress
    value below contains a pointer to the Gadget structure chosen
    by the user.    I use the GadgID field in the gadget structure
    to identify the gadget.    I don't necessarily need to know which
    requester had the gadget.   I could find out by looking at the
    FirstGadget field in the Window record,  then traversing through
    the linked list to the LAST requester in the list.


do_req(request)
struct Requester *request;          /* Which requester to process */
{
      int looping = TRUE;
      struct IntuiMessage *mes;
      struct Gadget *gad;           /* Gadget chosen */
      ULONG class;
 
      while (looping)
      {
          if ((mes = (struct IntuiMessage *)GetMsg(w->UserPort)) == 0L) {
             Wait(1L<<w->UserPort->mp_SigBit);
             continue;     /** Be nice to the other programs **/
          }
             class = mes->Class;
             gad = (struct Gadget *)mes->IAddress;
             ReplyMsg(mes);
             if (class == REQCLEAR) {
                looping = FALSE;          /* We exit and Intuition will */
             }                            /* remove the requester  */
             if (class == GADGETDOWN) {
 
                switch (gad->GadgetID) {
 
                   case TRUE_BUTT:  printf("true button ...\n");
                                    break;
                   case FALSE_BUTT: printf("false button...\n");
                                    break;
                   case FACE:       printf("Ouch!!..That hurt!!\n");
                                    break;
                }
             }
      }
}