                                Classic Menus

                                     "If gold rust what shall iren do?"
                                                 Chaucer 1340-1400

Introduction

          A classic menu is a menu displayed in a window (as shown in
     figure 10.1 below). In applications that don't use a desktop (like
     Quicken) the classic menu often forms the main screen of the
     program. The GOLDMENU unit provides pop-up menus with the following
     features:

         - Automatic prefixing of options with numbers, letters or
           function keys.
         - Support for disabled options.
         - Automatic menu placement with multiple column support.
         - Full mouse support.


     Figure 10.1
     A Classic Menu


Basic Concepts

     GOLDMENU defines the MenuRecord record as follows:

          MenuRecord = record
             Heading1     : string[MenuStrLength];
             Heading2     : string[MenuStrLength];
             Topic      : array[1..MaxChoices] of string[MenuStrLength];
             TotalPicks   : integer;
             PicksPerLine : byte;
             AddPrefix    : byte;
             TopLeftXY    : array[1..2] of byte;
             Boxtype      : byte;
             Colors       : array[1..5] of byte;
             Margins      : byte;
             AllowEsc     : boolean;
             Hook         : MenuHook;
             HindHook     : MenuHindHook;
          end; {MenuRecord}

          A menu record defines all the properties of the menu, i.e. the
     headings, the colors, the position, the menu items, etc. The basic
     approach to displaying a menu is to declare a variable of type
     MenuRecord, update the record variable with the menu items,
     headings etc., and call DisplayMenu to instruct Gold to display the
     menu and return when the user has made a selection.

          Listed below is an extract from the demo program DEMMEN1.PAS
     which illustrates this basic approach:

          var
            MainMenu: MenuRecord;
            Choice,Ecode: integer;


          procedure SetMenu;
          {}
          begin
             MenuSet(MainMenu);
             with MainMenu do
             begin
                Heading1     := 'TechnoJock''s Turbo Toolkit';
                Heading2     := 'Gold!';
                Topic[1]     := 'Unit Descriptions';
                Topic[2]     := '!Unit Demos';
                Topic[3]     := 'Self-Running Demo';
                Topic[4]     := 'How to register';
                Topic[5]     := 'About GOLD';
                Topic[6]     := '';
                Topic[7]     := 'Exit Demo';
                TotalPicks   := 7;
                Boxtype      := 5;
                AddPrefix    := 3;
                PicksPerLine := 1;
                AllowEsc     := false;
             end;
          end; { SetMenu }

          begin
             ...
             SetMenu;
             DisplayMenu(MainMenu,false,Choice,ECode);
             ...
          end.

Use MenuSet First!

          Important Note: Always, always, call the MenuSet function to
     initialize a menu record before assigning values to the various
     menu record components. MenuSet initializes all the menu record
     elements to suitable default values.

Managing the MenuRecord

     Listed below is a description of the main MenuRecord fields:

          Heading1 - The heading at the top of the menu. Set the string
               to null (the default) if you don't want a heading.

          Heading2 - The second heading at the top of the menu. Set the
               string to null (the default) if you don't want a heading.

          Topic - An array of the actual menu items that will be
               displayed in the menu. Start with the first element,
               assign menu items strings to each element of the array
               until you have fully defined the menu items, e.g.

                Topic[1]     := 'Unit Descriptions';
                Topic[2]     := '-Unit Demos';
                Topic[3]     := 'Self-Running Demo';
                Topic[4]     := 'How to register';
                Topic[5]     := 'About GOLD';
                Topic[6]     := '';
                Topic[7]     := 'Exit Demo';

          TotalPicks - The total number of items in the menu. In the
               above example, the value would be 7.

          PicksPerLine - Identifies how many items will be displayed on
               each line in the menu. Gold automatically adjusts this
               setting if the text is too long to fit on the screen.

          AddPrefix - Gold can automatically prefix the menu items with
               numbers, letters or function keys. By default, the prefix
               is zero which indicates that no prefix should be used.
               The other valid settings are 1, 2, 3 and 4 as follows:

               1 All menu options are prefixed with a single digit
                 number. If TotalPicks exceeds 9, an alpha prefix will
                 be used instead.
               2 All menu options are prefixed with a letter, starting
                 with A. If TotalPicks exceeds 26, the prefix is
                 ignored.
               3 All menu options are prefixed with a function key,
                 beginning with F1. If total picks exceeds 10, the Gold
                 will prefix with alphas instead.
               4 The first capital letter in each item is highlighted
                 and the user can select the item by pressing the
                 highlighted letter.

          TopLeftXY - A two byte array which identifies the X and Y
              coordinate for the top left corner of the menu -- Gold
              automatically determines the overall dimensions of the
              menu based upon the length of headings and items. If you
              specify coordinates too close to a screen edge, Gold
              automatically adjusts the menu position so that it is
              fully visible. When the X coordinate is set to 0, the menu
              is centered horizontally on the display. Similarly, a Y
              coordinate 0 will center the menu vertically. For example,
              if you want the menu to be displayed in the center of the
              screen starting on the seventh line, the declaration would
              be:

               MainMenu.TopLeftXY[1] := 0;
               MainMenu.TopLeftXY[2] := 7;

          BoxType - Indicates the window style, and should have a value
               in the range 0 to 2 or 5, as follows:

               0 No Border
               1 Single Line Border
               2 Double Line Border
               5 Quicken-style menu

          Colors - A five byte array which defines the menu display
               colors. Refer to the section Customizing Menu Colors
               (later) for more information.

          Margins - The number of spaces separating the menu border and
               the picks. Typical values range from 1 to 5.

          AllowEsc - A boolean variable which controls whether the Esc
               key is operative, i.e. whether or not the user can escape
               from the menu. When this field is set to FALSE, the
               escape key is ignored, otherwise the menu session
               finishes and the Retcode is set to 1.

A Few Tips and Tricks

          If a menu topic begins with the exclamation character (!), the
     menu item will be inactive, i.e. grayed out.

          If you want a blank line between two specific items, specify
     an item in between the two using a null string -- this approach is
     used in DEMMEN1.PAS on element 6.

          Don't forget that the menu will be automatically centered if
     the TopLeftXY[1] and TopLeftXY[2] elements are set to zero.

          By default, Gold supports a maximum of 30 items in a menu. If
     you need more than 30, just assign a higher value to the GOLDMENU
     constant MaxChoices.

Displaying the Menu

          Having called MenuSet and configured the menu record, use
     DisplayMenu (defined below) to display the menu.

     DisplayMenu(MenuDef: MenuRecord; Window:Boolean;
                           var Choice,Errorcode: integer);

          Displays a classic menu. MenuDef is the menu record defining
     the menu content. Set Window to true if you want the menu to be
     removed when a selection is made, or false if you want the menu to
     remain visible. The Choice variable is updated with the user's
     selection. Error codes are returned in the ErrorCode variable.

Controlling the Default Menu Selection

          The third parameter passed to DisplayMenu is a variable
     integer parameter which is updated with the selection made by the
     user, e.g. it will have a value of 7 if the user selects the
     seventh menu option.

          This variable also controls which menu item is highlighted
     when the menu is first displayed. Be sure to assign an appropriate
     value to the Choice variable before calling DisplayMenu.

Checking the Return Code

          If the user is allowed to escape (by setting the menu record
     field AllowEsc to TRUE), you will need to test the value of the
     ErrorCode parameter before launching the appropriate menu
     selection. If the user escaped from the menu, ErrorCode will be set
     to 1, otherwise it will have a value of zero.

Customizing the Menu Colors

          The default menu colors assigned to each individual menu can
     be set using the GoldSetColor procedure, assigning attributes to
     the following TINT elements:

          MenuHiHot
          MenuHi
          MenuNormHot
          MenuNorm
          MenuBorder


          The following code is an extract from DEMMEN2.PAS which
     assigns custom colors to the menu defaults:

          procedure CustomizeColors;
          {}
          begin
             GoldSetColor(MenuHiHot,YellowOnGreen);
             GoldSetColor(MenuHi,BlackonGreen);
             GoldSetColor(MenuNormHot,YellowOnMagenta);
             GoldSetColor(MenuNorm,WhiteOnMagenta);
             GoldSetColor(MenuOff,LightgrayonMagenta);
             GoldSetColor(MenuBorder,YellowOnMagenta);
          end; { CustomizeColors }

          The GoldSetColor procedure should be called prior to
     initializing the menu record with MenuSet.

          The technique just described controls the default colors for
     all menus. You can change the colors for an individual menu by
     modifying the values of the menu record's Colors array, after
     initializing the record with MenuSet. Each of the five elements of
     the array control the display colors of the menu as follows:

          Colors[1]  The highlighted item's prefix.
          Colors[2]  The text of the highlighted item.
          Colors[3]  The menu headings and the standard items prefix.
          Colors[4]  The text of the standard items.
          Colors[5]  The menu border color.

          The color of non-selectable items (i.e. items which start with
     the minus character) can only be altered by using the GoldSetColor
     method (described above) using the MenuOff element.

Using Menu Hooks

          Classic menus support two types of hooks: hind hooks and
     character hooks:

Using Hind Hooks

          The hind hook is called once when the menu is first displayed,
     and every time a keystroke or mouse click is processed by the menu.
     A hind hook is ideal for displaying a long description for the
     highlighted topic.

          For a procedure to be eligible as a pop-up menu hind hook it
     must adhere to the following rules:

          The procedure must be declared as a far procedure at the root
          level. Refer to the section Understanding Hooks in Chapter 3
          for further information.

          The procedure must be declared with two passed parameters. The
          first integer parameter indicates the active item. The second
          parameter must be a variable of type integer; if this variable
          is assigned a non-zero value, the menu will automatically
          terminate, and the Errorcode (passed to DisplayMenu) will be
          assigned the value.

     The following procedure declaration follows these rules:

          {$F+}
          procedure LongDesc(Choice:integer; var Ecode:integer);
          {Displays a long description for the highlighted topic}
          var Msg: StrScreen;
          begin
             ClearLine(24,WhiteOnBlue);
             case Choice of
               1: Msg := 'Provides descriptions of each unit';
               2: Msg := 'Demos showing the power of each unit';
               3: Msg := 'Launches into the self-running demo';
               4: Msg := 'Explains how you can register Gold';
               5: Msg := 'Displays program information';
               7: Msg := 'Stops the program';
             end;
             WriteCenter(24,UseTint,Msg);
          end; { LongDesc }
          {$F-}

          Having written a procedure following the rules, the menu
     record field HindHook should be set to the procedure, e.g.

          MainMenu.HindHook := LongDesc;

     The demo program DEMMEN3.PAS illustrates how to use a hind hook.

Using Character Hooks

          A classic menu character hook is a procedure which is called
     every time a key is pressed. The hooked procedure is called before
     the key is processed, i.e. before the character is handled by
     DisplayMenu. The hook is particularly useful for trapping special
     keys like F1 for help, or Alt-X to exit.

          For a procedure to be eligible as a pop-up menu character hook
     it must adhere to the following rules:

          The procedure must be declared as a far procedure at the root
          level. Refer to the section Understanding Hooks in Chapter 3
          for further information.

          The procedure must be declared with one variable parameter of
          type word, and variable of type integer, and a variable
          parameter of type integer. The first parameter indicates which
          key was pressed. the second parameter indicates which pick is
          highlighted. If this variable is assigned a non-zero value,
          the menu will automatically terminate, and the Errorcode
          (passed to DisplayMenu) will be assigned the value.


     The following procedure declaration follows these rules:

          {$F+}
          procedure CheckForKeys(var Key:word; Choice:integer;
                                                 var Ecode:integer);
          {Checks for F1 or Alt-X}
          begin
             if Key = 315 then
                .....
          end; { CheckForkeys }
          {$F-}

          Having written a procedure following the rules, the menu
     record field Hook should be set to the procedure, e.g.

          MainMenu.Hook := CheckForkeys;

          The demo program DEMMEN4.PAS illustrates how to use a pop-up
     menu character hook.

Structuring a Menu-Driven Program

          If you are writing an application which uses a Gold classic
     menu as the main menu, read on.

Using a Repeat-Until Loop

          DisplayMenu displays a menu and returns the value selected by
     the user. If you want an application to always return to the main
     menu after completing a task selected by the user, the DisplayMenu
     statement must be placed in some form of loop.

          Listed below is an extract from DEMMEN1.PAS which loops
     continuously back to the main menu:

          SetScreen;
          SetMenu;
          MouseShow(true);
          Choice := 5;
          repeat
             DisplayMenu(MainMenu,false,Choice,ECode);
             case Choice of
                1: PromptOK(' Pretend ','Descriptions');
                2: ;
                3: PromptOK(' Pretend ','Self-Running Demo');
                4: PromptOK(' Pretend ','How to register');
                5: PromptOK(' About ','^Gold||Copyr.....');
             end;
          until Choice = 7;
          MouseShow(false);
          clrscr;

          Notice that the initial value of Choice is set before the menu
     is first displayed, and that the repeat loop is terminated when the
     user selects option 7 -- the quit option. Also, note that the code
     includes a case statement after DisplayMenu to branch to another
     part of the program based on the user's selection.

     That's all there is to it!

Created Nested Menus

          The following extract from DEMMEN5.PAS illustrates how to
     create nested menus. In this example, when a user selects item 1
     from the main menu, a sub-menu of additional items is displayed.
     Pressing Esc in the sub-menu will return the user back to the main
     menu.

          Choice := 1;
          SubChoice := 1;
          repeat
             DisplayMenu(M1,false,Choice,ECode);
             case Choice of
                1: begin
                   repeat
                      DisplayMenu(MM,true,SubChoice,ECode);
                      if Ecode <> 0 then
                         SubChoice := 5;
                      case SubChoice of
                        1: ;
                        2: ;
                        {etc.}
                      end;
                   until SubChoice = 5;
                end;
                2..4: PromptOK(' Selection ','You chose...       end;
          until Choice = 5;
          MouseShow(false);
          clrscr;

          Notice there is a nested case statement to handle separately
     the main and sub-menu choices, and that different variables are
     used to track the menu selections: Choice and SubChoice.

