;/* menulayout.c - Execute me to compile me with SAS C 5.10 LC -b1 -cfistq -v -y -j73 menulayout.c Blink FROM LIB:c.o,menulayout.o TO menulayout LIBRARY LIB:LC.lib,LIB:Amiga.lib quit ** menulayout.c - Example showing how to do menu layout in general. This example ** also illustrates handling menu events, including IDCMP_MENUHELP events. ** ** Note that handling arbitrary fonts is fairly complex. Applications that require V37 ** should use the simpler menu layout routines found in the GadTools library. */ #define INTUI_V36_NAMES_ONLY #include <exec/types.h> #include <intuition/intuition.h> #include <intuition/intuitionbase.h> #include <graphics/gfxbase.h> #include <dos/dos.h> #include <clib/exec_protos.h> #include <clib/graphics_protos.h> #include <clib/intuition_protos.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #ifdef LATTICE int CXBRK(void) { return(0); } /* Disable Lattice CTRL/C handling */ int chkabort(void) { return(0); } /* really */ #endif /* Our function prototypes */ BOOL processMenus(USHORT selection, BOOL done); BOOL handleIDCMP(struct Window *win); USHORT MaxLength(struct RastPort *textRPort, struct MenuItem *first_item, USHORT char_size); VOID setITextAttr(struct IntuiText *first_IText, struct TextAttr *textAttr); VOID adjustItems(struct RastPort *textRPort, struct MenuItem *first_item, struct TextAttr *textAttr, USHORT char_size, USHORT height, USHORT level, USHORT left_edge); BOOL adjustMenus(struct Menu *first_menu, struct TextAttr *textAttr); LONG doWindow(void); /* Settings Item IntuiText */ struct IntuiText SettText[] = { {0,1,JAM2,2, 1, NULL, "Sound...", NULL }, {0,1,JAM2,CHECKWIDTH,1, NULL, " Auto Save", NULL }, {0,1,JAM2,CHECKWIDTH,1, NULL, " Have Your Cake", NULL }, {0,1,JAM2,CHECKWIDTH,1, NULL, " Eat It Too", NULL } }; struct MenuItem SettItem[] = { { /* "Sound..." */ &SettItem[1], 0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP, 0, (APTR)&SettText[0], NULL, NULL, NULL, MENUNULL }, { /* "Auto Save" (toggle-select, initially selected) */ &SettItem[2], 0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP|CHECKIT|MENUTOGGLE|CHECKED, 0, (APTR)&SettText[1], NULL, NULL, NULL, MENUNULL }, { /* "Have Your Cake" (initially selected, excludes "Eat It Too") */ &SettItem[3], 0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP|CHECKIT|CHECKED, 8, (APTR)&SettText[2], NULL, NULL, NULL, MENUNULL }, { /* "Eat It Too" (excludes "Have Your Cake") */ NULL, 0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP|CHECKIT, 4, (APTR)&SettText[3], NULL, NULL, NULL, MENUNULL } }; /* Edit Menu Item IntuiText */ struct IntuiText EditText[] = { {0,1,JAM2,2,1, NULL, "Cut", NULL }, {0,1,JAM2,2,1, NULL, "Copy", NULL }, {0,1,JAM2,2,1, NULL, "Paste", NULL }, {0,1,JAM2,2,1, NULL, "Erase", NULL }, {0,1,JAM2,2,1, NULL, "Undo", NULL } }; /* Edit Menu Items */ struct MenuItem EditItem[] = { { /* "Cut" (key-equivalent: 'X') */ &EditItem[1], 0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&EditText[0], NULL, 'X', NULL, MENUNULL }, { /* "Copy" (key-equivalent: 'C') */ &EditItem[2], 0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&EditText[1], NULL, 'C', NULL, MENUNULL }, { /* "Paste" (key-equivalent: 'V') */ &EditItem[3], 0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&EditText[2], NULL, 'V', NULL, MENUNULL }, { /* "Erase" (disabled) */ &EditItem[4], 0, 0, 0, 0, ITEMTEXT|HIGHCOMP, 0, (APTR)&EditText[3], NULL, NULL, NULL, MENUNULL }, { /* "Undo" MenuItem (key-equivalent: 'Z') */ NULL, 0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&EditText[4], NULL, 'Z', NULL, MENUNULL } }; /* IntuiText for the Print Sub-Items */ struct IntuiText PrtText[] = { {0,1, JAM2,2,1, NULL, "NLQ", NULL }, {0,1, JAM2,2,1, NULL, "Draft", NULL } }; /* Print Sub-Items */ struct MenuItem PrtItem[] = { { /* "NLQ" */ &PrtItem[1], 0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP, 0, (APTR)&PrtText[0], NULL, NULL, NULL, MENUNULL }, { /* "Draft" */ NULL, 0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP, 0, (APTR)&PrtText[1], NULL, NULL, NULL, MENUNULL } }; /* Uses the >> character to indicate a sub-menu item. ** This is \273 Octal, 0xBB Hex or Alt-0 from the Keyboard. ** ** NOTE that standard menus place this character at the right margin of the menu box. ** This may be done by using a second IntuiText structure for the single character, ** linking this IntuiText to the first one, and positioning the IntuiText so that the ** character appears at the right margin. GadTools library will provide the correct behavior. */ /* Project Menu Item IntuiText */ struct IntuiText ProjText[] = { {0,1, JAM2,2,1, NULL, "New", NULL }, {0,1, JAM2,2,1, NULL, "Open...", NULL }, {0,1, JAM2,2,1, NULL, "Save", NULL }, {0,1, JAM2,2,1, NULL, "Save As...", NULL }, {0,1, JAM2,2,1, NULL, "Print \273", NULL }, {0,1, JAM2,2,1, NULL, "About...", NULL }, {0,1, JAM2,2,1, NULL, "Quit", NULL } }; /* Project Menu Items */ struct MenuItem ProjItem[] = { { /* "New" (key-equivalent: 'N' */ &ProjItem[1],0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[0], NULL, 'N', NULL, MENUNULL }, { /* "Open..." (key-equivalent: 'O') */ &ProjItem[2],0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[1], NULL, 'O', NULL, MENUNULL }, { /* "Save" (key-equivalent: 'S') */ &ProjItem[3],0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[2], NULL, 'S', NULL, MENUNULL }, { /* "Save As..." (key-equivalent: 'A') */ &ProjItem[4],0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[3], NULL, 'A', NULL, MENUNULL }, { /* "Print" (has sub-menu) */ &ProjItem[5],0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[4], NULL, NULL, &PrtItem[0], MENUNULL }, { /* "About..." */ &ProjItem[6],0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[5], NULL, NULL, NULL, MENUNULL }, { /* "Quit" (key-equivalent: 'Q' */ NULL, 0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[6], NULL, 'Q', NULL, MENUNULL } }; /* Menu Titles */ struct Menu Menus[] = { {&Menus[1], 0, 0, 63, 0, MENUENABLED, "Project", &ProjItem[0]}, {&Menus[2], 70, 0, 39, 0, MENUENABLED, "Edit", &EditItem[0]}, {NULL, 120, 0, 88, 0, MENUENABLED, "Settings", &SettItem[0]}, }; /* A pointer to the first menu for easy reference */ struct Menu *FirstMenu = &Menus[0]; /* Window Text for Explanation of Program */ struct IntuiText WinText[] = { {0, 0, JAM2, 0, 0, NULL, "How to do a Menu", NULL}, {0, 0, JAM2, 0, 0, NULL, "(with Style)", &WinText[0]} }; /* Globals */ struct Library *IntuitionBase = NULL; struct Library *GfxBase = NULL; /* open all of the required libraries. Note that we require ** Intuition V37, as the routine uses OpenWindowTags(). */ VOID main(int argc, char **argv) { LONG returnValue; /* This gets set to RETURN_OK if everything goes well. */ returnValue = RETURN_FAIL; /* Open the Intuition Library */ IntuitionBase = OpenLibrary("intuition.library", 37); if (IntuitionBase) { /* Open the Graphics Library */ GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 33); if (GfxBase) { returnValue = doWindow(); CloseLibrary(GfxBase); } CloseLibrary(IntuitionBase); } exit(returnValue); } /* Open a window with some properly positioned text. Layout and set ** the menus, then process any events received. Cleanup when done. */ LONG doWindow() { struct Window *window; struct Screen *screen; struct DrawInfo *drawinfo; ULONG signalmask, signals; ULONG win_width, alt_width, win_height; LONG returnValue = RETURN_FAIL; BOOL done = FALSE; if (screen = LockPubScreen(NULL)) { if (drawinfo = GetScreenDrawInfo(screen)) { /* get the colors for the window text */ WinText[0].FrontPen = WinText[1].FrontPen = drawinfo->dri_Pens[TEXTPEN]; WinText[0].BackPen = WinText[1].BackPen = drawinfo->dri_Pens[BACKGROUNDPEN]; /* use the screen's font for the text */ WinText[0].ITextFont = WinText[1].ITextFont = screen->Font; /* calculate window size */ win_width = 100 + IntuiTextLength(&(WinText[0])); alt_width = 100 + IntuiTextLength(&(WinText[1])); if (win_width < alt_width) win_width = alt_width; win_height = 1 + screen->WBorTop + screen->WBorBottom + (screen->Font->ta_YSize * 5); /* calculate the correct positions for the text in the window */ WinText[0].LeftEdge = (win_width - IntuiTextLength(&(WinText[0]))) >> 1; WinText[0].TopEdge = 1 + screen->WBorTop + (2 * screen->Font->ta_YSize); WinText[1].LeftEdge = (win_width - IntuiTextLength(&(WinText[1]))) >> 1; WinText[1].TopEdge = WinText[0].TopEdge + screen->Font->ta_YSize; /* Open the window */ window = OpenWindowTags(NULL, WA_PubScreen, screen, WA_IDCMP, IDCMP_MENUPICK | IDCMP_CLOSEWINDOW | IDCMP_MENUHELP, WA_Flags, WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_CLOSEGADGET | WFLG_ACTIVATE | WFLG_NOCAREREFRESH, WA_Left, 10, WA_Top, screen->BarHeight + 1, WA_Width, win_width, WA_Height, win_height, WA_Title, "Menu Example", WA_MenuHelp, TRUE, TAG_END); if (window) { returnValue = RETURN_OK; /* program initialized ok */ /* Give a brief explanation of the program */ PrintIText(window->RPort,&WinText[1],0,0); /* Adjust the menu to conform to the font (TextAttr) */ adjustMenus(FirstMenu, window->WScreen->Font); /* attach the menu to the window */ SetMenuStrip(window, FirstMenu); /* Set up the signals that you want to hear about ... */ signalmask = 1L << window->UserPort->mp_SigBit; /* And wait to hear from your signals */ while (!done) { signals = Wait(signalmask); if (signals & signalmask) done = handleIDCMP(window); }; /* clean up everything used here */ ClearMenuStrip(window); CloseWindow(window); } FreeScreenDrawInfo(screen,drawinfo); } UnlockPubScreen(NULL,screen); } return(returnValue); } /* print out what menu was selected. Properly handle the IDCMP_MENUHELP ** events. Set done to TRUE if quit is selected. */ BOOL processMenus(USHORT selection, BOOL done) { USHORT flags; USHORT menuNum, itemNum, subNum; menuNum = MENUNUM(selection); itemNum = ITEMNUM(selection); subNum = SUBNUM(selection); /* when processing IDCMP_MENUHELP, you are not guaranteed ** to get a menu item. */ if (itemNum != NOITEM) { flags = ((struct MenuItem *)ItemAddress(FirstMenu,(LONG)selection))->Flags; if (flags & CHECKED) printf("(Checked) "); } switch (menuNum) { case 0: /* Project Menu */ switch (itemNum) { case NOITEM: printf("Project Menu\n"); break; case 0: printf("New\n"); break; case 1: printf("Open\n"); break; case 2: printf("Save\n"); break; case 3: printf("Save As\n"); break; case 4: printf("Print "); switch (subNum) { case NOSUB: printf("Item\n"); break; case 0: printf("NLQ\n"); break; case 1: printf("Draft\n"); break; } break; case 5: printf("About\n"); break; case 6: printf("Quit\n"); done = TRUE; break; } break; case 1: /* Edit Menu */ switch (itemNum) { case NOITEM: printf("Edit Menu\n"); break; case 0: printf("Cut\n"); break; case 1: printf("Copy\n"); break; case 2: printf("Paste\n"); break; case 3: printf("Erase\n"); break; case 4: printf("Undo\n"); break; } break; case 2: /* Settings Menu */ switch (itemNum) { case NOITEM: printf("Settings Menu\n"); break; case 0: printf("Sound\n"); break; case 1: printf("Auto Save\n"); break; case 2: printf("Have Your Cake\n"); break; case 3: printf("Eat It Too\n"); break; } break; case NOMENU: /* No menu selected, can happen with IDCMP_MENUHELP */ printf("no menu\n"); break; } return(done); } /* Handle the IDCMP messages. Set done to TRUE if quit or closewindow is selected. */ BOOL handleIDCMP(struct Window *win) { BOOL done; USHORT code, selection; struct IntuiMessage *message = NULL; ULONG class; done = FALSE; /* Examine pending messages */ while (message = (struct IntuiMessage *)GetMsg(win->UserPort)) { class = message->Class; code = message->Code; /* When we're through with a message, reply */ ReplyMsg((struct Message *)message); /* See what events occurred */ switch (class) { case IDCMP_CLOSEWINDOW: done = TRUE; break; case IDCMP_MENUHELP: /* ** The routine that handles the menus for IDCMP_MENUHELP must be very careful ** it can receive menu information that is impossible under IDCMP_MENUPICK. ** For instance, the code value on a IDCMP_MENUHELP may have a valid number ** for the menu, then NOITEM and NOSUB. IDCMP_MENUPICK would get MENUNULL ** in this case. IDCMP_MENUHELP never come as multi-select items, and the ** event terminates the menu processing session. ** ** Note that I do not keep the return value from the processMenus() routine here--the ** application should not quit if the user selects "help" over the quit menu item. */ printf("IDCMP_MENUHELP: Help on "); processMenus(code,done); break; case IDCMP_MENUPICK: for ( selection = code; selection != MENUNULL; selection = (ItemAddress(FirstMenu,(LONG)selection))->NextSelect) { printf("IDCMP_MENUPICK: Selected "); done = processMenus(selection,done); } break; } } return(done); } /* Steps thru each item to determine the maximum width of the strip */ USHORT MaxLength(struct RastPort *textRPort, struct MenuItem *first_item, USHORT char_size) { USHORT maxLength; USHORT total_textlen; struct MenuItem *cur_item; struct IntuiText *itext; USHORT extra_width; USHORT maxCommCharWidth; USHORT commCharWidth; extra_width = char_size; /* used as padding for each item. */ /* Find the maximum length of a command character, if any. ** If found, it will be added to the extra_width field. */ maxCommCharWidth = 0; for (cur_item = first_item; cur_item != NULL; cur_item = cur_item->NextItem) { if (cur_item->Flags & COMMSEQ) { commCharWidth = TextLength(textRPort,&(cur_item->Command),1); if (commCharWidth > maxCommCharWidth) maxCommCharWidth = commCharWidth; } } /* if we found a command sequence, add it to the extra required space. Add ** space for the Amiga key glyph plus space for the command character. Note ** this only works for HIRES screens, for LORES, use LOWCOMMWIDTH. */ if (maxCommCharWidth > 0) extra_width += maxCommCharWidth + COMMWIDTH; /* Find the maximum length of the menu items, given the extra width calculated above. */ maxLength = 0; for (cur_item = first_item; cur_item != NULL; cur_item = cur_item->NextItem) { itext = (struct IntuiText *)cur_item->ItemFill; total_textlen = extra_width + itext->LeftEdge + TextLength(textRPort, itext->IText, strlen(itext->IText)); /* returns the greater of the two */ if (total_textlen > maxLength) maxLength = total_textlen; } return(maxLength); } /* Set all IntuiText in a chain (they are linked through the NextText ** field) to the same font. */ VOID setITextAttr(struct IntuiText *first_IText, struct TextAttr *textAttr) { struct IntuiText *cur_IText; for (cur_IText = first_IText; cur_IText != NULL; cur_IText = cur_IText->NextText) cur_IText->ITextFont = textAttr; } /* Adjust the MenuItems and SubItems */ VOID adjustItems(struct RastPort *textRPort, struct MenuItem *first_item, struct TextAttr *textAttr, USHORT char_size, USHORT height, USHORT level, USHORT left_edge) { register USHORT item_num; struct MenuItem *cur_item; USHORT strip_width, subitem_edge; if (first_item == NULL) return; /* The width of this strip is the maximum length of its members. */ strip_width = MaxLength(textRPort, first_item, char_size); /* Position the items. */ for (cur_item = first_item, item_num = 0; cur_item != NULL; cur_item = cur_item->NextItem, item_num++) { cur_item->TopEdge = (item_num * height) - level; cur_item->LeftEdge = left_edge; cur_item->Width = strip_width; cur_item->Height = height; /* place the sub_item 3/4 of the way over on the item. */ subitem_edge = strip_width - (strip_width >> 2); setITextAttr((struct IntuiText *)cur_item->ItemFill, textAttr); adjustItems(textRPort,cur_item->SubItem,textAttr,char_size,height,1,subitem_edge); } } /* The following routines adjust an entire menu system to conform to the specified fonts' width and ** height. Allows for Proportional Fonts. This is necessary for a clean look regardless of what the ** users preference in Fonts may be. Using these routines, you don't need to specify TopEdge, ** LeftEdge, Width or Height in the MenuItem structures. ** ** NOTE that this routine does not work for menus with images, but assumes that all menu items are ** rendered with IntuiText. ** ** This set of routines does NOT check/correct if the menu runs off ** the screen due to large fonts, too many items, lo-res screen. */ BOOL adjustMenus(struct Menu *first_menu, struct TextAttr *textAttr) { struct RastPort textrp = {0}; /* Temporary RastPort */ struct Menu *cur_menu; struct TextFont *font; /* Font to use */ USHORT start, char_size, height; BOOL returnValue = FALSE; /* open the font */ if (font = OpenFont(textAttr)) { SetFont(&textrp, font); /* Put font into temporary RastPort */ char_size = TextLength(&textrp, "n", 1); /* Get the Width of the Font */ /* To prevent crowding of the Amiga key when using COMMSEQ, don't allow the items to be less ** than 8 pixels high. Also, add an extra pixel for inter-line spacing. */ if (font->tf_YSize > 8) height = 1 + font->tf_YSize; else height = 1 + 8; start = 2; /* Set Starting Pixel */ /* Step thru the menu structure and adjust it */ for (cur_menu = first_menu; cur_menu != NULL; cur_menu = cur_menu->NextMenu) { cur_menu->LeftEdge = start; cur_menu->Width = char_size + TextLength(&textrp, cur_menu->MenuName, strlen(cur_menu->MenuName)); adjustItems(&textrp, cur_menu->FirstItem, textAttr, char_size, height, 0, 0); start += cur_menu->Width + char_size + char_size; } CloseFont(font); /* Close the Font */ returnValue = TRUE; } return(returnValue); }