home *** CD-ROM | disk | FTP | other *** search
- /*
- * The functions for pop-up menus
- *
- * Written by Derek Zahn (Gambit Software, Madison WI), July 1987
- *
- * This code is freely distributable and is blessed by its author for
- * inclusion, in this form or any other, into Amiga programs,
- * commercial or non-commercial. If this is done, no credit must be
- * given to me (although I wouldn't mind).
- *
- * This code was developed and tested under Manx Aztec C, version 3.40a
- * with small code, small data, and short integers as part of the Gambit
- * Software development environment. It has been "unGambitized" for
- * general use. I am unfamiliar with other Amiga C compilers, so cannot
- * speculate on any porting difficulties. This file was created with a
- * text editor (Z) whose tabstops were set to 8, so that it may be easily
- * and intelligibly printed. This code was developed under 1.2; I am
- * not sure if it will work under 1.1, but can't see why not.
- *
- * Note that there are some features that should be supported but are not,
- * and some issues about the function and interface that make me nervous.
- * These are explained in the appendix to the documentation. I would
- * greatly appreciate receiving any enhancements and modifications to this
- * code, or suggestions therefor. Comments on techniques and coding
- * style are always appreciated. Enjoy.
- */
-
- /* include files */
-
- #include <exec/types.h>
- #include <intuition/intuitionbase.h>
- #include <intuition/intuition.h>
- #include <graphics/gfxmacros.h>
- #include "popmenu.h"
- #include "exec/memory.h"
-
- /* Externally defined functions used in this module */
-
- extern struct Window *OpenWindow();
- extern struct IntuiMessage *GetMsg(); /* type coercion, true... */
-
- extern VOID CloseWindow(), ReplyMsg(), Wait();
- extern VOID RectFill(), Move(), Draw(), Text(), PrintIText(), DrawImage();
-
- /* The following functions are defined in this module */
-
- extern LONG PopChoose(); /* blocking user interface */
- /* -- exported */
- extern SHORT pop_computestate(); /* see who is selected, */
- /* if anybody */
- extern VOID pop_highlight(); /* highlight the specified item */
- extern VOID pop_unhighlight(); /* unhighlight the specified item */
- extern VOID pop_do_highlighting(); /* high or un high light the item */
- extern VOID pop_render(); /* draws the title (if existent) */
- /* and menu items */
- extern VOID pop_draw_menuitem(); /* draws the menu item */
- extern struct MenuItem *pop_getitem(); /* find a MenuItem struc */
- extern SHORT pop_strlen(); /* local strlen() */
-
- /* This is structure will be used to create a window for display of the */
- /* menu. In my heart of hearts, I wanted to use graphics library */
- /* functions instead, but reason prevailed. Note the use of the RMBTRAP */
- /* flag -- while the pop-up menu is being processed, there is no use for */
- /* the right button. Perhaps this should only be set if the right button */
- /* has some bearing on the pop-up menu. */
-
- static struct NewWindow pop_window = {
- 0, 0, /* LeftEdge, TopEdge: will be */
- /* filled in later */
- 0, 0, /* Width, Height: will be */
- /* filled in later */
- (UBYTE) -1, (UBYTE) -1, /* BlockPen, DetailPen */
- MOUSEBUTTONS | MOUSEMOVE, /* IDCMP flags */
- SMART_REFRESH | REPORTMOUSE | ACTIVATE | RMBTRAP, /* flags */
- NULL, /* no gadgets */
- NULL, /* checkmark inherited later */
- NULL, /* no title */
- NULL, /* Screen -- will be filled in later */
- NULL, /* No custom bitmap */
- 0, 0, /* MinWidth, MinHeight -- no change */
- /* in size necessary */
- 0, 0, /* MaxWidth, MaxHeight -- no change */
- /* in size necessary */
- CUSTOMSCREEN /* always use this value */
- };
-
- /* It is assumed that the following point to bases of opened libraries */
-
- extern struct IntuitionBase *IntuitionBase;
- extern struct GfxBase *GfxBase;
-
- /* ============================================================= */
-
- /*
- * PopChoose (menu, win)
- * menu -- pointer to the menu to pop
- * win -- the window to which this menu relates. NULL means the currently
- * active window.
- *
- * This function provides a blocking pop-up menu. It returns (LONG) -1 if
- * either an error occurred attempting to pop or if no selection was made
- * by the user. If a selection was made, a LONG between 0 and n-1, where
- * n is the number of Menu Items.
- *
- * -1 is also returned if a selection of a checked item was made.
- *
- * Since this code opens a window, it is up to the caller to be sure that
- * no scribbling in droll ways is done while this code is in progress.
- */
-
- LONG PopChoose (menu, win)
-
- struct Menu *menu;
- struct Window *win;
-
- {
-
- extern UWORD *arrow; /* pointer to data for mouse */
- extern int arrow_x_offset, arrow_y_offset, arrow_size;
-
- struct Screen *screen; /* the window's screen */
- struct Window *popwin; /* the pop-up menu */
- struct IntuiMessage *message; /* our eyes and ears */
- struct MenuItem *sel_item; /* the selected item */
- SHORT pop_state, pop_newstate; /* menu selection state varaibles */
- SHORT mouse_moved; /* keeps track of whether the */
- /* mouse has moved */
- SHORT finished; /* set when menu should be blown away */
- SHORT class; /* incoming IntuiMessage class */
- SHORT code; /* incoming IntuiMessage code */
- ULONG exclude; /* for handling mutual exclusion */
-
- /* Check to see that IntuitionBase and GfxBase are non-null. */
- /* While this is not any sort of guarantee against disaster, it */
- /* is better than nothing. */
-
- if ((IntuitionBase == NULL) || (GfxBase == NULL))
- return ((LONG) (-1));
-
- /* One paranoid check */
-
- if (menu == NULL)
- return ((LONG) (-1));
-
- /* If the menu is not MENUENABLED, nothing to do */
-
- if (!(menu->Flags & MENUENABLED))
- return ((LONG) (-1));
-
- /* Form the menu window to blast forth into the Visual World. Note */
- /* the unconventional (and inconsistent with Intuition) ways that */
- /* the Width and Height fields are used here. */
-
- pop_window.Width = menu->Width;
- pop_window.Height = menu->Height;
-
- if (win == NULL)
- win = IntuitionBase->ActiveWindow;
- if (win == NULL) /* panic */
- return ((LONG) (-1));
-
- /* Inherit CheckMark from the "parent" window */
-
- if (win->CheckMark)
- pop_window.CheckMark = win->CheckMark;
-
- screen = win->WScreen;
- pop_window.Screen = screen;
-
- pop_window.LeftEdge = menu->LeftEdge;
- pop_window.TopEdge = menu->TopEdge;
-
- /* if we are supposed to return to the last-selected menu item and */
- /* such a beast exists, all other positioning information (except */
- /* POPTIDY) will be circumvented. The menu will appear under the */
- /* pointer with the last-chosen item pre-selected, if this is */
- /* possible given the POPTIDY flag and the screen constraints. */
- /* In this case, the LeftEdge and TopEdge fields of the menu */
- /* structure will have been altered (I know, ick!) to provide a */
- /* relative offset with respect to the pointer to do the deed */
-
- if ((menu->Flags & POPREMEMBER) && (menu->Flags & POPUSED)) {
- pop_window.LeftEdge += screen->MouseX;
- pop_window.TopEdge += screen->MouseY;
- }
- else {
- if (menu->Flags & POPPOINTREL) {
- pop_window.LeftEdge += screen->MouseX;
- pop_window.TopEdge += screen->MouseY;
- }
- else if (menu->Flags & POPWINREL) {
- pop_window.LeftEdge += win->LeftEdge;
- pop_window.TopEdge += win->TopEdge;
- }
- }
-
- /* If the caller wishes us to be POPTIDY, the menu must completely */
- /* appear on the screen, whatever other effects this may have on */
- /* menu positioning. The left edge and top edge must be altered */
- /* accordingly. In the pathological case where the menu is larger */
- /* than the screen, -1 is returned. */
- /* If poptidiness is not a factor, the size of the window may have */
- /* to be altered if it shoots off the bottom or right edge of the */
- /* screen. There should be some similar mechanism to deal with */
- /* the menu if it extends past the top or left edge of the screen; */
- /* as it stands now, the OpenWindow() call will fail, and the */
- /* result may be even more dire under 1.1. Use 1.2! */
-
- if (menu->Flags & POPTIDY) {
- if ((pop_window.Width > screen->Width) ||
- (pop_window.Height > screen->Height))
- return ((LONG) (-1));
- if (pop_window.LeftEdge + pop_window.Width > screen->Width)
- pop_window.LeftEdge = screen->Width-pop_window.Width;
- if (pop_window.TopEdge + pop_window.Height > screen->Height)
- pop_window.TopEdge=screen->Height-pop_window.Height;
- if (pop_window.LeftEdge < screen->LeftEdge)
- pop_window.LeftEdge = screen->LeftEdge;
- if (pop_window.TopEdge < screen->TopEdge)
- pop_window.TopEdge = screen->TopEdge;
- }
- else {
- if (pop_window.LeftEdge + pop_window.Width > screen->Width)
- pop_window.Width = screen->Width - pop_window.LeftEdge;
- if (pop_window.TopEdge + pop_window.Height > screen->Height)
- pop_window.Height = screen->Height - pop_window.TopEdge;
- }
-
- /* There! Finally, the window is ready to be displayed! First, */
- /* create it. */
-
- popwin = OpenWindow (&pop_window);
- if (popwin == NULL) /* all that work for nuthin' */
- return ((LONG) (-1));
-
- SetPointer (popwin, arrow, arrow_size/4-2, 16,
- arrow_x_offset, arrow_y_offset);
-
- /* Now, render the menu items and (possibly) the menu title. */
-
- pop_render (popwin, menu);
-
- /* Now, see if the pointer is over a selection. The variable */
- /* 'pop_state' will from this point on hold the value, in linear */
- /* traversal order of the MenuItems (zero-indexed), the currently */
- /* selected menu item, or -1 if none are selected. */
-
- pop_state = pop_computestate (popwin, menu);
-
- /* If one is indeed currently selected, highlight it. */
-
- if (pop_state >= 0)
- pop_highlight (popwin, menu, pop_state);
-
- /* Here is the IDCMP loop that will process the pop-up menu. Note */
- /* that on mousemove events, I don't care where it moved, just if */
- /* it did -- pop_computestate() will figure out where by reaching */
- /* into the Window structure. Not Pure Programming, somehow, but */
- /* blessed by the Intuition manual. */
-
- finished = 0;
- while (1) {
- mouse_moved = 0;
- Wait ((ULONG) 1L << popwin->UserPort->mp_SigBit);
- while (message = GetMsg (popwin->UserPort)) {
- class = message->Class;
- code = message->Code;
- ReplyMsg (message);
-
- /* The only messages we should be getting are */
- /* mouse button and move events. Button events */
- /* could signify the end of this routine's */
- /* epheremal spotlight role. */
-
- switch (class) {
- case MOUSEMOVE:
- mouse_moved = 1;
- break;
- case MOUSEBUTTONS:
- switch (code) {
- case SELECTDOWN:
- if ((menu->Flags & POPLEFTBUTTON) &&
- (menu->Flags & POPTRIGGERDOWN))
- finished = 1;
- break;
- case SELECTUP:
- if ((menu->Flags & POPLEFTBUTTON) &&
- (menu->Flags & POPTRIGGERUP))
- finished = 1;
- break;
- case MENUDOWN:
- if ((menu->Flags & POPRIGHTBUTTON) &&
- (menu->Flags & POPTRIGGERDOWN))
- finished = 1;
- break;
- case MENUUP:
- if ((menu->Flags & POPRIGHTBUTTON) &&
- (menu->Flags & POPTRIGGERUP))
- finished = 1;
- break;
- default: /* huh? */
- break;
- }
- break;
- default: /* huh? */
- break;
- }
- }
-
- /* if the exit conditions have been met, we can return our */
- /* results with honor and dignity, having served. */
- /* Note that if we are remembering the last selection, the */
- /* menu structure is mangled to make that possible. */
-
- if (finished) {
- pop_state = pop_computestate (popwin, menu);
- if (pop_state >= 0) {
- if (menu->Flags & POPREMEMBER) {
- menu->Flags |= POPUSED;
- menu->LeftEdge = -1 * popwin->MouseX;
- menu->TopEdge = -1 * popwin->MouseY;
- }
-
- /* Special things to do if the menu entry */
- /* is of type CHECKIT */
-
- sel_item = pop_getitem (menu, pop_state);
- if (sel_item->Flags & CHECKIT) {
- if (sel_item->Flags & CHECKED) {
- pop_state = -1;
- if (sel_item->Flags & MENUTOGGLE)
- sel_item->Flags &= ~CHECKED;
- }
- else {
- sel_item->Flags |= CHECKED;
-
- /* Handle mutual exclusion */
-
- exclude = sel_item->MutualExclude;
- if (exclude) {
- sel_item = menu->FirstItem;
- while (sel_item) {
- if (exclude & 1)
- sel_item->Flags &= ~CHECKED;
- exclude >>= 1;
- sel_item = sel_item->NextItem;
- }
- }
- }
- }
- }
- ClearPointer (popwin);
- CloseWindow (popwin);
- return ((LONG) pop_state);
- }
-
- /* if the mouse has moved, find out its new state and */
- /* alter the highlighting accordingly. */
-
- if (mouse_moved) {
- pop_newstate = pop_computestate (popwin, menu);
- if (pop_newstate != pop_state) {
- if (pop_state >= 0)
- pop_unhighlight (popwin, menu, pop_state);
- if (pop_newstate >= 0)
- pop_highlight (popwin, menu, pop_newstate);
- pop_state = pop_newstate;
- }
- }
- }
- }
-
- /* ============================================================= */
-
- /*
- * pop_computestate()
- *
- * This function checks to see where the mouse pointer is in relation to
- * the various menu items in the menu. If it is inside one of them, it
- * returns which one (indexed by its linear position in the MenuItem list
- * with 0 being the first one). If not, returns -1.
- *
- * Possible future enhancement: keep a set of state variables containing
- * the UL and LR corners of the last-known select box; this would make
- * a quick check possible and would cut down the computation for short
- * mouse movements (the most common).
- */
-
- static SHORT pop_computestate (win, menu)
- struct Window *win;
- struct Menu *menu;
-
- {
-
- register SHORT current = 0;
- register SHORT xval, yval;
- register struct MenuItem *item;
-
- /* Get the x and y vals of the mouse position */
-
- xval = win->MouseX;
- yval = win->MouseY;
-
- /* If there is a title, decrement the yval by the correct amount */
-
- if (menu->MenuName)
- yval -= POPTITLEHEIGHT;
-
- /* First, see if the pointer is even in the window */
-
- if ((xval < 0) || (yval < 0) ||
- (xval > win->Width) || (yval > win->Height))
- return (-1);
-
- /* search through the list of menu items, checking the select box */
- /* of each. If containment is detected, the job is done. */
-
- item = menu->FirstItem;
- while (item) {
- if ((xval >= item->LeftEdge) && (yval >= item->TopEdge) &&
- (xval <= item->LeftEdge + item->Width) &&
- (yval <= item->TopEdge + item->Height)) {
-
- /* We have found the quarry; now, the result only */
- /* depends on the MenuItem's ITEMENABLED flag. */
-
- if (item->Flags & ITEMENABLED)
- return (current);
- else
- return (-1);
- }
- current++;
- item = item->NextItem;
- }
-
- /* If the list is exhausted, return the sad news */
-
- return (-1);
- }
-
- /* ============================================================= */
-
- /*
- * pop_highlight()
- *
- * highlight a menu item
- */
-
- static VOID pop_highlight (win, menu, state)
-
- struct Window *win;
- struct Menu *menu;
- SHORT state;
-
- {
-
- pop_do_highlighting (win, menu, state, 0);
-
- }
-
- /* ============================================================= */
-
- /*
- * pop_unhighlight()
- *
- * unhighlight a menu item
- */
-
- static VOID pop_unhighlight (win, menu, state)
-
- struct Window *win;
- struct Menu *menu;
- SHORT state;
-
- {
-
- pop_do_highlighting (win, menu, state, 1);
-
- }
-
- /* ============================================================= */
-
- /*
- * pop_do_highlighting()
- *
- * Highlight or unhighlight a menu item, given its traversal number. Assumes
- * this is a rational value -- if it isn't, Watch Out.
- */
-
- static VOID pop_do_highlighting (win, menu, state, mode)
-
- struct Window *win;
- struct Menu *menu;
- SHORT state;
- SHORT mode; /* 0 means to highlight, */
- /* 1 means to unhighlight */
-
- {
-
- register struct MenuItem *item;
- struct RastPort *rp;
- SHORT offset = 0;
-
- if (menu->MenuName)
- offset = POPTITLEHEIGHT;
-
- /* Get the correct MenuItem structure */
-
- item = pop_getitem (menu, state);
-
- rp = win->RPort;
-
- /* Now, do the highlighting! The action to be taken depends on */
- /* the type of highlighting desired for this item. */
- /* The way that the flags for highlighting works is truly bizarre */
-
- if ((item->Flags & HIGHNONE) == HIGHNONE)
- return;
-
- if (item->Flags & HIGHCOMP) {
- SetDrMd (rp, COMPLEMENT);
- RectFill (rp, (LONG) item->LeftEdge, (LONG) (item->TopEdge +
- offset), (LONG) (item->LeftEdge + item->Width - 1),
- (LONG) (item->TopEdge + item->Height + offset));
- }
- else if (item->Flags & HIGHBOX) {
- SetDrMd (rp, COMPLEMENT);
- Move (rp, (LONG) item->LeftEdge, (LONG) (item->TopEdge +
- offset));
- Draw (rp, (LONG) (item->LeftEdge + item->Width - 1),
- (LONG) (item->TopEdge + offset));
- Draw (rp, (LONG) (item->LeftEdge + item->Width - 1),
- (LONG) (item->TopEdge + item->Height + offset));
- Draw (rp, (LONG) item->LeftEdge,
- (LONG) (item->TopEdge + item->Height + offset));
- Draw (rp, (LONG) item->LeftEdge, (LONG)
- (item->TopEdge + offset));
- }
-
- /* Otherwise, the mode is HIGHIMAGE */
-
- else
- pop_draw_menuitem (win, item, !mode, offset);
- }
-
- /* ============================================================= */
-
- /*
- * pop_render()
- *
- * renders the menu title (if existent) and the menu items
- */
-
- static VOID pop_render (win, menu)
-
- struct Window *win;
- struct Menu *menu;
-
- {
-
- struct MenuItem *item;
- struct RastPort *rp;
- SHORT offset = 0;
-
- rp = win->RPort;
-
- /* Fill the background with color 1, like Intuition Menus */
-
- SetAPen (rp, 1L);
- RectFill (rp, 0L, 0L, (LONG) win->Width, (LONG) win->Height);
-
- /* First, if there is a Title for this menu, render it in the top */
- /* of the menu. */
-
- if (menu->MenuName) {
- SetDrMd (rp, JAM1);
- SetAPen (rp, 0L);
- SetBPen (rp, 1L);
- Move (rp, 4L, 7L);
- Text (rp, menu->MenuName, (LONG) pop_strlen(menu->MenuName));
- SetDrMd (rp, COMPLEMENT);
- RectFill (rp,0L,0L, (LONG) win->Width, (LONG) POPTITLEHEIGHT);
- SetDrMd (rp, JAM1);
- offset = POPTITLEHEIGHT;
- }
-
- /* now render all of the menu items */
-
- item = menu->FirstItem;
- while (item) {
- pop_draw_menuitem (win, item, 0, offset);
- item = item->NextItem;
- }
- }
-
- /* ============================================================= */
-
- /* Area fill patterns */
-
- static USHORT pop_ghost_pattern[] = {
- 0x1111, 0x4444
- };
- static USHORT pop_normal_pattern[] = {
- 0xffff, 0xffff
- };
-
- /* ============================================================= */
-
- /*
- * pop_draw_menuitem()
- *
- * Draws the specified menuitem in the given rastport. The mode argument
- * says what to draw -- 0 means draw the ItemFill, 1 the SelectFill.
- */
-
- static VOID pop_draw_menuitem (win, item, mode, offset)
-
- struct Window *win;
- struct MenuItem *item;
- SHORT mode;
- SHORT offset;
-
- {
-
- APTR fill;
- struct RastPort *rp;
-
- /* first, figure out what to do, and return if it is a NULL thing */
-
- if (!mode)
- fill = item->ItemFill;
- else
- fill = item->SelectFill;
-
- if (!fill)
- return;
-
- rp = win->RPort;
-
- /* First, erase what may already be there, just to be sure that */
- /* everything works out all right. */
-
- SetAPen (rp, 1L);
- SetDrMd (rp, JAM1);
- RectFill (rp, (LONG) item->LeftEdge, (LONG) (item->TopEdge +
- offset), (LONG) (item->LeftEdge + item->Width), (LONG)
- (item->TopEdge + item->Height + offset));
-
- /* If the item is checkmarked, draw the checkmark. Intuition made */
- /* sure that the CheckMark field of the window structure exists */
-
- if (item->Flags & CHECKIT)
- if (item->Flags & CHECKED)
- DrawImage (rp, win->CheckMark, (LONG) item->LeftEdge,
- (LONG) (item->TopEdge + offset + 1));
-
- /* Now, draw the item itself -- depending on the Flag value, it */
- /* could be either an Image or an IntuiText */
-
- if (item->Flags & ITEMTEXT)
- PrintIText (rp, fill, (LONG) item->LeftEdge,
- (LONG) (item->TopEdge + offset));
- else
- DrawImage (rp, fill, (LONG) item->LeftEdge,
- (LONG) (item->TopEdge + offset));
-
- /* If the ITEMENABLED flag is not set, "ghost" the item. */
-
- if (!(item->Flags & ITEMENABLED)) {
- SetAPen (rp, 1L);
- SetDrMd (rp, JAM1);
- SetAfPt (rp, (USHORT *) pop_ghost_pattern, 1L);
- RectFill (rp, (LONG) item->LeftEdge, (LONG) (item->TopEdge +
- offset), (LONG) (item->LeftEdge + item->Width), (LONG)
- (item->TopEdge + item->Height + offset));
- SetAfPt (rp, (USHORT *) pop_normal_pattern, 1L);
- }
- }
-
- /* ============================================================= */
-
- /*
- * pop_getitem()
- *
- * given the traversal number of a menu item in a menu (assumes, BTW, that
- * the arguments are valid), return a pointer to the MenuItem structure
- */
-
- static struct MenuItem *pop_getitem (menu, which)
-
- struct Menu *menu;
- SHORT which;
-
- {
-
- struct MenuItem *item;
-
- item = menu->FirstItem;
- while (which--)
- item = item->NextItem;
- return (item);
- }
-
- /* ============================================================= */
-
- /*
- * pop_strlen()
- *
- * a home-brewed strlen to prevent it being necessary to hook in whatever
- * huge object file in which the c library's strlen() resides.
- */
-
- static SHORT pop_strlen (str)
-
- char *str;
-
- {
-
- register SHORT count = 0;
-
- for (; *str++; count++);
- return (count);
- }
-
- /* :-) */
-