;/* 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);
}