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. 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