[comp.sys.atari.st] OSS Personal Pascal tutorial #5

DAVIDLI@SIMVAX.BITNET (Dave Meile) (12/08/87)

This is another OSS Personal Pascal tutorial.  You can put it up on BBS
systems, on user disks, etc.  I just don't want to see them come out in
a book (without my name on it... :-)  )

Length c420 lines:
----------------------------------CUT HERE-------------------------------------

                                 Now What?
               OSS Personal Pascal and the beginner - part 5
                           written by David Meile

     [Copyright 1987 David Meile, all rights reserved.  Permission is
     given to Atari user groups to reprint this article, as long as
     this statement is included.  OSS Personal Pascal is a product of
     Optimized Systems Software, Inc.  I am not related to the company,
     I simply bought their compiler.]

        In this fifth article on programming using OSS Personal Pascal,
     we will take a look at the "how-to" of creating GEM menus.

        Creating menus is similar in many ways to creating dialog
     boxes.  First, we allocate space for the menu.  Then we describe
     the titles that will appear on our menu bar.  Then we describe the
     items that will appear under each title of the menu bar.  After
     that, we define the characteristics (if any) for those menu items.
     Then we DRAW the menu on the screen.

        At this point, we depart from the way dialog boxes are handled.
     The reason is that GEM menus require the use of an "event
     manager".  This manager receives messages from GEM about
     keypresses, mouse point-and-clicks, window manipulation, etc. and
     responds according to how we want our program to handle these
     messages.  Since this column is directed towards menus, I won't go
     into all of the options for event management -- instead I will
     show you the minimum amount of event management required to handle
     menus.

        Once we are done with a menu, we can erase it from the screen.
     If our program has no further need of such a menu, we can delete
     the menu from the computer memory, freeing up some space for other
     things in our program.

                             ****WARNING****

         As released by OSS, both version 1.0 and 2.0 have a "bug".  You
     cannot run a program which calls MENU_TEXT sucessfully until you
     remove the three letters "VAR" from the parameter list for
     the procedure Menu_Text (located in GEMSUBS.PAS).  Make this a
     permanent change to that particular file, and all should go well
     when you compile.

                               AN EXAMPLE ...

        Take a moment now to look over the program 'NowWhat5'.  I'll be
     explaining it at the end of the listing.

     PROGRAM NowWhat5;

     { This program demonstrates the creation and manipulation of
       GEM menus using OSS Personal Pascal. }

     {$I gemsubs.pas}

     {NOTE: version 2.0 owners, use this with the new gemsubs.pas.
            version 1.0 owners, use the individual 'includes' of
             gemconst.pas, gemtypes.pas and the old gemsubs.pas. }

     VAR
       ap_id     : integer;

     PROCEDURE Do_Menu;

     VAR
       le_menu   : Menu_Ptr; { menu tree }
       le_file   : integer;  { menu title }
       le_select : integer;  { menu title }
       le_show   : integer;  { menu title }

       load      : integer; { menu item }
       quit      : integer; { menu item }
       joke1     : integer; { menu item }
       joke2     : integer; { menu item }
       joke3     : integer; { menu item }
       show      : integer; { menu item }
       null1     : integer; { menu item }
       some1     : integer; { menu item }
       some2     : integer; { menu item }
       some3     : integer; { menu item }

       op1, op2, op3, bye, active : boolean;

       junk, event, dummy         : integer; { for event management }
       msg                        : Message_Buffer; { for event management }

       all_str, some_str, none_str : Str255;
       s1_str, s2_str, s3_str      : Str255;
       astring                     : Str255;

     BEGIN { Do_Menu }

       { Initialize variables and text strings. }

       op1 := False;
       op2 := False;
       op3 := False;

       all_str  := ' Show all Jokes      ';
       none_str := ' No jokes selected   ';
       some_str := ' Show selected Jokes ';
       s1_str   := 'Why did the chicken cross..';
       s2_str   := 'How many lightbulbs does...';
       s3_str   := 'What do you get when you ..';
       astring := '[1][ Now What? article 5 | by David Meile ][ Okay ]';

       { Set up the menu. }

       le_menu := New_Menu( 20, ' About NowWhat 5... ' );

       le_file   := Add_MTitle( le_menu, '  Files  ' );
       le_select := Add_MTitle( le_menu, '  Select  ' );
       le_show   := Add_MTitle( le_menu, '  Show  ' );

       load := Add_MItem( le_menu, le_file, '  Load ' );
       quit := Add_MItem( le_menu, le_file, '  Quit ' );

       joke1 := Add_MItem( le_menu, le_select, '  Joke File 1 ' );
       joke2 := Add_MItem( le_menu, le_select, '  Joke File 2 ' );
       joke3 := Add_MItem( le_menu, le_select, '  Joke File 3 ' );

       show   := Add_MItem( le_menu, le_show, none_str );
       null1 := Add_MItem( le_menu, le_show, '-----------------------' );
       some1 := Add_MItem( le_menu, le_show, '   Joke File 1   ' );
       some2 := Add_MItem( le_menu, le_show, '   Joke File 2   ' );
       some3 := Add_MItem( le_menu, le_show, '   Joke File 3   ' );

       Menu_Check( le_menu, joke1, op1 );
       Menu_Check( le_menu, joke2, op2 );
       Menu_Check( le_menu, joke3, op3 );
       Menu_Disable( le_menu, some1 );
       Menu_Disable( le_menu, some2 );
       Menu_Disable( le_menu, some3 );

       Menu_Disable( le_menu, null1 );

       { Allow selection of various menu options.  Exit only when the
         QUIT options is chosen from the menu. }

       Draw_Menu( le_menu );

       bye := False;
       REPEAT


         IF NOT( op1 AND op2 AND op3 ) THEN
           BEGIN
             active := False;
             Menu_Text( le_menu, show, none_str )
           END;

         IF ( op1 OR op2 OR op3 ) THEN
           BEGIN
             active := True;
             Menu_Text( le_menu, show, some_str )
           END;

         IF ( op1 AND op2 AND op3 ) THEN
           BEGIN
             active := True;
             Menu_Text( le_menu, show, all_str );
           END;

         event := Get_Event( E_Message, 0,0,0,0, False, 0,0,0,0,
                             False, 0,0,0,0, msg, dummy, dummy, dummy,
                             dummy, dummy, dummy );

         { Erase old text on screen }

         Draw_String( 60,150, '                           ' );
         Draw_String( 60,170, '                           ' );
         Draw_String( 60,190, '                           ' );

         { msg[3] = 3 when the 'About ...' from the DESK menu is chosen}

         IF msg[3] = 3 THEN
           junk := Do_Alert( astring, 1 );

         IF msg[3] = le_file THEN
           IF msg[4] = load THEN
             BEGIN
               IF NOT( op1 OR op2 OR op3 ) THEN
                 Draw_String( 60,150, 'Select a joke file first...' )
               ELSE
                 Draw_String( 60,150, 'Loading jokes...           ' )
             END
           ELSE IF msg[4] = quit THEN
             bye := True;

         IF msg[3] = le_select THEN
           IF msg[4] = joke1 THEN
             BEGIN
               op1 := NOT( op1 );
               Menu_Check( le_menu, joke1, op1 );
               IF op1 THEN
                 Menu_Enable( le_menu, some1 )
               ELSE
                 Menu_Disable( le_menu, some1 )
             END
           ELSE IF msg[4] = joke2 THEN
             BEGIN
               op2 := NOT( op2 );
               Menu_Check( le_menu, joke2, op2 );
               IF op2 THEN
                 Menu_Enable( le_menu, some2 )
               ELSE
                 Menu_Disable( le_menu, some2 )
             END
           ELSE IF msg[4] = joke3 THEN
             BEGIN
               op3 := NOT( op3 );
               Menu_Check( le_menu, joke3, op3 );
               IF op3 THEN
                 Menu_Enable( le_menu, some3 )
               ELSE
                 Menu_Disable( le_menu, some3 )
             END; { IF }

         IF msg[3] = le_show THEN
           BEGIN
             IF msg[4] = show THEN
               IF active THEN
                 BEGIN
                   IF op1 THEN
                     Draw_String( 60,150, s1_str );
                   IF op2 THEN
                     Draw_String( 60,170, s2_str );
                   IF op3 THEN
                     Draw_String( 60,190, s3_str );
                 END;
             IF msg[4] = some1 THEN
               IF op1 THEN
                 Draw_String( 60,150, s1_str );
             IF msg[4] = some2 THEN
               IF op2 THEN
                 Draw_String( 60,150, s2_str );
             IF msg[4] = some3 THEN
               IF op3 THEN
                 Draw_String( 60,150, s3_str );
           END;
        Menu_Normal( le_menu, msg[3] );
       UNTIL bye;
       Erase_Menu( le_menu );
       Delete_Menu( le_menu );

     END; { Do_Menu }

     BEGIN { NowWhat5 }

       ap_id := Init_Gem;
       IF ap_id >= 0 THEN
         BEGIN
           Init_Mouse;
           Hide_Mouse;
           Clear_Screen;
           Show_Mouse;
           Text_color( Red );
           Draw_Mode( 1 );
           Do_Menu;
           Exit_Gem;
         END;
     END. { NowWhat5 }

                               AN EXPLANATION

        A menu requires a pointer, titles for the desktop menu bar and
     items to go under those titles.  Locate the section titled { Set
     up the menu. }.  Note that it is somewhat similar to the way we
     set up dialog boxes last column.

        First, we identify a specific menu by name, in this case I
     called it 'le_menu'.  The function that does this is NEW_MENU.
     It's parameters are (1) the number of items to be associated with
     this menu (including menu titles and items) and (2) the text
     string which will go under the DESK menu title -- which is usually
     something like "About this program".

        Next, we identify the other titles that will appear on the menu
     bar at the top of our screen, using the function ADD_MTITLE. The
     parameters are the menu pointer (le_menu), and the text string
     identifying the menu title.  Here, 'le_file' refers to the menu
     title "Files".  Similarly 'le_show' refers to the menu title
     "Show".  The variables will be used when adding specific items
     under the menu titles.

        After adding all of the titles, we add items to go under the
     menu titles, using the function ADD_MITEM.  It's parameters are
     (1) the menu pointer, (2) the menu title and (3) the specific text
     string that will refer to the item.  We will use the item
     variables when we wish to change the appearance of a particular
     text string.

        MENU_CHECK adds a check-mark to the left of a menu item.  For
     that reason, you should put at least two spaces to the left of a
     text string referring to a menu item, so there is enough room for
     a check mark.  All of the menu items are defined in the program
     with two spaces to the left of the text string.  MENU_CHECK has a
     boolean variable as the last parameter -- you can tell it to check
     or "un"check a menu item depending on whether the variable is True
     or False.  The menu item to be checked is referred to by both the
     menu pointer and the menu item.

        MENU_DISABLE has a complementary procedure called MENU_ENABLE.
     Both procedures are called in the program NowWhat5.  MENU_DISABLE
     produces "ghost" writing -- the item is grey instead of black,
     showing that the item is unavailable.  Also, when an item is
     disabled you can click on it all you want, but the event handler
     will not recognize it.

                           WITHIN THE REPEAT LOOP

        All of this takes us to the beginning of the actual work within
     the program NowWhat5: the REPEAT loop.  First we set the variable
     that will cause the program to repeat the loop until it is
     changed.  The variable is 'bye', and it will only be set to a True
     value if we select the Quit option from the menu we've created.

        The three IF statements test to see whether we have chosen
     anything under the Select menu.  Depending on whether none, some,
     or all items under the Select menu have check marks by them, we
     display an appropriate message at the top of the Show menu.

        Right after the IF statements is the heart of the repeat loop -
     the call to the event manager, GET_EVENT.  There are many
     variables and options for this function, and I won't go into them
     all here, as I'll be doing so in a later column.  The important
     things to know are:

        1) E_Message is a message event.  Here, it is used to indicate
           that the user has clicked on one of the menu items.

        2) msg is the message buffer.  There are many sorts of messages
           which may be returned, but we are only interested in two
           elements of that message buffer:

               msg[3] refers to the menu title which has been selected
               msg[4] refers to the menu item selected

     If there were more than one event possible in this program, we
     would first have to test whether msg[0] referred to a MN_SELECTED
     state before assuming msg[3] and msg[4] referred to a GEM menu.
     Please make note of this if you also are handling other window
     events at the same time (more about this will appear in the next
     column).

        A menu-event where msg[3] = 3 always refers to the 'About' item
     under the Desk menu title.  In the program, we draw an alert box
     with information about the program when we select the 'About...'
     item.

        What follow are a large number of IF statements. There is an
     outer IF statement (IF msg[3] = xxx) and one or more inner IF
     statements (IF msg[4] = xxx). They test to see which menu title is
     referred to (msg[3]) and then which message item is reffered to
     (msg[4]).  Then, depending on what the menu item actually is,
     several other statements may be executed.

        For example, if msg[3] = le_file and msg[4] = quit then we set
     the variable 'bye' to True, which enables us to exit the repeat
     loop.

        For the Select title, we toggle between the checked and
     unchecked state using the statement

               opn  := NOT(opn)

     then, depending on the state of 'opn' we enable or disable the
     corresponding menu item under the Show title.

        For the Show title, we can select the upper default, which will
     print out a joke if it is enabled, or we can select an individual
     joke if it is enabled.  Of course, when all jokes are disabled
     nothing will be shown.

        The logic of the IF statements may seem to be complex, but if
     you actually run the program, you will be able to see what happens
     when you select the various menu items.  If you have desk
     accessories, I would suggest you do not select them from the Desk
     menu title as they will have unpredictable results within this
     particular program (due to the very minor "event management" going
     on.)

        Finally, before we repeat the loop we make a call to
     Menu_Normal using the menu title (referred to by msg[3]).  The
     reason this is necessary is that GEM will highlight that menu
     title when you select a menu item, and will NOT return it to
     normal automatically.

        When we are done with the menu, we can erase it from the screen
     using ERASE_MENU and then remove it from computer memory using
     DELETE_MENU.  Of course, if you're going to use a particular menu
     more than once in a program, it makes sense not to delete it.  You
     can issue a call to DRAW_MENU after you've called ERASE_MENU, and
     all of the menu items will be in the same state as before.

                               ANOTHER THING

        You will notice that I have not created a normal window in this
     program.  Instead, I made a call to CLEAR_SCREEN instead.  As a
     precaution, I first hid the mouse, then cleared the screen, then
     showed the mouse again.   When you only want a blank page upon
     which to draw graphics or menus or dialog boxes, you can use this
     call.  Clear_Screen will cover ALL WINDOWS, regardless, so don't
     use it in a case where you have multiple windows (or where you are
     going to create windows).  The full-screen window will not go away
     until you end your program.  Be aware.

                                FINAL WORDS

        I have been entirely too long getting this article out, and I
     apologize.  I hope that you have found the previous articles
     useful.  I fully intend to complete this series "real soon", and
     then, perhaps, to go onto some more difficult programming
     articles.

        Feedback is appreciated.  If your user group uses this article
     in a newsletter, I'd appreciate seeing a copy.  Mail will reach me
     at the following address:

        David Meile
        Box 13038
        Minneapolis, MN  55414