home *** CD-ROM | disk | FTP | other *** search
- /*
- * Quick Menu Package -- An easy way to make simple (but nice) menus.
- *
- * The client programmer will generally use the GenMenu()/FreeMenu()
- * interface which creates and frees a whole menu strip. A menu strip
- * -- both the main strip and menu item strips -- is defined by a
- * NewMenu struct. Each NewMenu struct (defined in qmenu.h) is just a
- * pointer to an array of strings and a pointer to an array of
- * NewMenu's. The elements of the array of NewMenu's are paired with the
- * strings in the array and represent the sub menus for that string.
- * The string array is terminated with a null pointer.
- *
- * The package tries to do nice things, like automatically formatting
- * the menu strings into blocks with their command keys, and placing
- * markers pointing to subitem stips. The strings for the menu items
- * can contain special control codes to control the optional
- * characteristics of items. Special codes come at the front of item
- * strings and are delimited with a special character (SPECIAL,
- * defined as '!' by default) and are listed briefly below:
- *
- * c Checkmark item
- * t Checkmark toggle
- * + Checkmark checked
- * b Highlight box
- * n Highlight none
- * d Disabled
- * =C Set command key to "C"
- * 0101... Set item exclude mask
- *
- * Any number of 1's and 0's after the special character will set the
- * Mutual exclude bits for that item in the order they occur. So the
- * first 1 or 0 will set bit 0 of the exclude mask, the second will
- * set bit 1, etc. Any unset will be 0.
- *
- * Additionally, if the item string is "-", the item will be
- * a non-selectable horizontal line (a "rule") that can be used to
- * visually group similar items in the menu strip. There only need
- * to be NewMenu structs in the array for text menu items, not rules,
- * but don't forget to account for rules when counting MenuItem's
- * since they take a slot.
- *
- *
- * GenMenu() takes a pointer to a NewMenu struct and will create a main
- * Menu strip from the strings in the struct, with menu items from the
- * associated NewMenu's. It returns a pointer to a Menu struct which is
- * the first element in the list for this strip.
- *
- * GenStrip() takes a pointer to an array of strings and creates a
- * top-level set of Menu structs for the main menu strip.
- *
- * FreeMenu() frees a Menu strip created with either GenMenu() or
- * GenStrip().
- *
- * GenItems() takes a pointer to a NewMenu struct and creates a single
- * list of MenuItem's from the description. It also takes an X and Y
- * offset for all the elements in the list.
- *
- * FreeMItem() frees structures created by the above function.
- *
- * AttachSubMenu() takes a single MenuItem and a NewMenu struct and
- * attaches the MenuItems created from that NewMenu struct to the
- * parent MenuItem. The parent should also have been created with
- * GenItems() in order to work correctly. AttachSubMenu() places a
- * little maker on the parent item to show that it has subitems. It
- * returns 1 for sucess and 0 for failure.
- *
- * -- WARNING:
- *
- * The client programmer is completely responsible for keeping the
- * menus within the bounds of the screen and for keeping within the
- * limits of Intuition. The menu structures will not adjust as they
- * hit the borders of the screen (which might be a nice enhancement).
- * The strings in the NewMenu structures cannot be changed or deleted
- * while the Menus are in use, although they can be used to create
- * multiple Menus from the same strings.
- *
- **
- * This code can be used and modified freely. Do let me know of any
- * improvements or enhancements you may add, and be sure to give credit
- * where credit is due (in the comments) if you redistribute this code.
- *
- * Stuart Ferguson 1/89
- * (shf@well.UUCP)
- */
- #include <intuition/intuition.h>
- #include "qmenu.h"
-
- /*
- * Some useful macros for allocating and freeing structures and
- * arrays of structures.
- */
- #define NEW(typ) (typ*)AllocMem((long)sizeof(typ),0L)
- #define FREI(p) FreeMem(p,(long)sizeof(*p))
-
- #define NEW_N(typ,n) (typ*)AllocMem((long)((n)*sizeof(typ)),0L)
- #define FREI_N(p,n) FreeMem(p,(long)((n)*sizeof(*p)))
-
- char * AllocMem();
-
- /*
- * Definitions for formatting the menus. Glossary:
- *
- * TEXTCOLOR - color of the text items in the menus.
- * MARKCOLOR - color of the subitem marker.
- * RULECOLOR - color of the horizontal lines in the menus.
- * RULEHEIGHT - vertical thickness of the horizontal rules.
- * RULEGAP - vertical blank space around the rules.
- * RULEMARGIN - horizontal blank space around the rules.
- * TEXTGAP - vertical blank space around the text items.
- * TEXTMARGIN - horizontal blank space around the text items.
- * MAINWID - width of the text font on the main menu.
- * MAINGAP - spacing between items on the main menu strip.
- * SUBINDENT - overlap between items and their subitems.
- * SUBDOWN - vertical shift between items and their subitems.
- */
- #define TEXTCOLOR 2
- #define MARKCOLOR 3
- #define RULECOLOR 0
- #define RULEHEIGHT 2
- #define RULEGAP 4
- #define RULEMARGIN 10
- #define TEXTGAP 2
- #define TEXTMARGIN 4
- #define MAINWID 10
- #define MAINGAP 10
- #define SUBINDENT 18
- #define SUBDOWN 0
-
- /*
- * Escape character for special control codes in text items strings.
- */
- #define SPECIAL '!'
-
- /*
- * The extern "ta" is set by the client program for
- * the font to use for these menus.
- */
- extern struct TextAttr ta;
-
- /*
- * Generic templates to use for creating the dynamic menu structures.
- */
- static struct IntuiText
- generic_itext = {TEXTCOLOR, 0, JAM1, TEXTMARGIN/2, TEXTGAP/2, &ta,NULL,NULL},
- generic_smark = {MARKCOLOR, 0, JAM1, 0, TEXTGAP/2, &ta, (UBYTE *) ";", NULL};
-
- static struct Menu
- generic_main = {NULL, 0, 0, 0, 10, MENUENABLED, NULL, NULL};
-
- static struct MenuItem
- generic_mitem = {
- NULL, 0, 0, 0, 0,
- ITEMTEXT | ITEMENABLED | HIGHCOMP,
- 0, NULL, NULL, 0, NULL, 0
- };
-
- /* Image struct with no imagery for the horizontal lines.
- */
- static struct Image
- generic_hrule = {0, 0, 1, RULEHEIGHT, 2, NULL, 0, RULECOLOR, NULL};
-
- static struct MenuItem
- generic_hitem = {
- NULL, RULEMARGIN/2, 0, 0, 0,
- ITEMENABLED | HIGHNONE,
- 0, NULL, NULL, 0, NULL, 0
- };
-
-
- /*
- * Takes an array of strings and associated array of NewMenu structs
- * (as a single NewMenu struct) and constructs a menu strip from the
- * descripton. This is the main high-level call that most clients
- * will make.
- */
- struct Menu * GenMenu (nmen)
- register struct NewMenu *nmen;
- {
- register short i, ok, n;
- register struct Menu *mm;
- register struct MenuItem *mi;
-
- /* Count menus to be generated and create top level structure.
- */
- for (n = 0; nmen->str[n]; n++) ;
- mm = GenStrip (nmen->str);
- if (!mm) return NULL;
-
- /* Create the item strip for each main menu and attach to the
- * top level Menu structure. Any failure causes the whole
- * thing to crumble to dust.
- */
- ok = 1;
- nmen = nmen->submenu;
- for (i = 0; i < n; i++) {
- if (nmen->str) {
- mi = GenItems (nmen, 0L, 0L);
- mm[i].FirstItem = mi;
- ok &= (mi != NULL);
- }
- nmen ++;
- }
- if (!ok) {
- FreeMenu (mm);
- return NULL;
- }
- return mm;
- }
-
-
- /*
- * Generate a menu strip. Just creates the top level Menu structures,
- * linked together and intialized. Takes an array of pointers to
- * strings.
- */
- struct Menu * GenStrip (str)
- char **str;
- {
- register short i, x, num;
- register struct Menu *mm, *m;
-
- /*
- * Create enough struct Menu's for the menu strip.
- */
- for (num = 0; str[num]; num++) ;
- mm = NEW_N (struct Menu, num);
- if (!mm) return NULL;
-
- /*
- * Init the structures using the generic Menu struct as a template.
- * NOTE: the size of the font for these item labels is unknown for
- * windows on the Workbench screen, so a size should be used
- * that will work with 60 & 80 column fonts.
- */
- x = 0;
- for (i = 0; i < num; i++) {
- m = &mm[i];
- *m = generic_main;
- m->LeftEdge = x;
- m->Width = strlen (str[i]) * MAINWID + TEXTMARGIN;
- x += m->Width + MAINGAP;
- m->MenuName = str[i];
- m->NextMenu = m + 1;
- }
- mm[num - 1].NextMenu = NULL;
- return mm;
- }
-
-
- /*
- * Attach a submenu to a MenuItem. Takes the parent MenuItem and a
- * NewMenu structure that will be attached as a sub-menu.
- * Attaches a ";" mark at the end of the parent item and calls
- * GenItems() to create the sub-menu strip.
- */
- BOOL AttachSubMenu (mi, nmen)
- register struct MenuItem *mi;
- struct NewMenu *nmen;
- {
- register struct IntuiText *it;
-
- /* Create an IntuiText with the marker and position it
- * at the right edge of the item.
- */
- if (!(it = NEW (struct IntuiText))) return FALSE;
- *it = generic_smark;
- it->LeftEdge = mi->Width - IntuiTextLength (it) - 2;
-
- /* Create the subitem structure and attach to the main item.
- */
- if (!(mi->SubItem = GenItems
- (nmen, (LONG) (mi->Width - SUBINDENT), (LONG) SUBDOWN))) {
- FREI (it);
- return FALSE;
- }
-
- /* Only if it all worked attach the new text structure.
- */
- ((struct IntuiText *) mi->ItemFill)->NextText = it;
- return TRUE;
- }
-
-
- /*
- * Takes the given menu text item and skips past the special control
- * codes while adjusting the associated MenuItem appropriately.
- * Special codes are in the form of "!x", where "x" is:
- *
- * c Checkmark item
- * t Checkmark toggle
- * b Highlight box
- * n Highlight none
- * d Disabled
- * + Checkmark checked
- * =C Set command key to "C"
- * 1010... Set item exclude mask
- *
- * Takes the mostly defined MenuItem and diddles it. Returns a pointer
- * to the item text with control codes stripped.
- */
- static UBYTE * ProcessSpecialStuff (str, mi)
- char *str;
- struct MenuItem *mi;
- {
- register LONG x;
- register int i;
-
- while (*str == SPECIAL) {
- switch (*++str) {
- case 'c':
- mi->Flags |= CHECKIT;
- break;
- case 't':
- mi->Flags |= CHECKIT | MENUTOGGLE;
- break;
- case 'b':
- mi->Flags &= ~HIGHFLAGS;
- mi->Flags |= HIGHBOX;
- break;
- case 'n':
- mi->Flags &= ~HIGHFLAGS;
- mi->Flags |= HIGHNONE;
- break;
- case 'd':
- mi->Flags &= ~ITEMENABLED;
- break;
- case '+':
- mi->Flags |= CHECKIT | CHECKED;
- break;
- case '=':
- mi->Flags |= COMMSEQ;
- mi->Command = *++str;
- break;
- case '0':
- case '1':
- x = 0;
- i = 0;
- while (*str == '0' || *str == '1')
- x += (*str++ - '0') << (i++);
- mi->MutualExclude = x;
- str--;
- break;
- }
- str++;
- }
- return (UBYTE*) str;
- }
-
-
- /*
- * Construct a basic item list for a menu. Takes a NewMenu structure
- * which contains a pointer to an array of pointers to strings and a
- * pointer to an array of NewMenu structures. The strings contain the
- * item text for each menu plus optional special control codes. If the
- * string is "-", the item will be a horizontal rule rather than a text
- * item. The NewMenu structures, if not NULL, are the sub-menu's for
- * each menu in the array.
- * "x" and "y" are the horizontal and vertical offsets of this set of
- * MenuItems. These are set by AttachSubMenu() for positioning submenus
- * under their parent items.
- */
- struct MenuItem *GenItems (nmen, x, y)
- struct NewMenu *nmen;
- LONG x, y;
- {
- register struct MenuItem *mi, *cmi;
- register struct IntuiText *itext;
- struct Image *img;
- register short i, len, max;
- short n;
- struct NewMenu *sub;
-
- /* Count menu items (n) and allocate an array for the strip.
- */
- for (n = 0; nmen->str[n]; n++) ;
- if (!(mi = NEW_N (struct MenuItem, n))) return NULL;
-
- /* Counts the number of rules in the menu ("-" strings)
- * and allocates the structures for the lines and the text items.
- */
- max = 0;
- for (i = 0; i < n; i++) max += (*nmen->str[i] == '-');
- if (n - max)
- if (!(itext = NEW_N (struct IntuiText, n - max))) {
- FREI_N (mi, n);
- return NULL;
- }
- if (max)
- if (!(img = NEW_N (struct Image, max))) {
- FREI_N (mi, n);
- if (n - max) FREI_N (itext, n - max);
- return NULL;
- }
-
- /* Loop through text menu items and initialize the
- * associated IntuiText structures. Compute the maximum
- * width of the menu taking command keys into account while
- * assigning all the other parts of the text MenuItem's.
- */
- max = 0;
- for (i = 0; i < n; i++) {
- if (*nmen->str[i] == '-') continue; /* skip rules */
-
- /* Init the text MenuItem to point to the assocd IntuiText.
- */
- cmi = &mi[i];
- *cmi = generic_mitem;
- cmi->ItemFill = (APTR) itext;
-
- /* Init the IntuiText and adjust the MenuItem from the
- * flags set in the menu text string.
- */
- *itext = generic_itext;
- itext->IText = ProcessSpecialStuff (nmen->str[i], cmi);
-
- /* Make a first cut at measuring the length of the item.
- */
- len = IntuiTextLength (itext) + TEXTMARGIN;
-
- /* If command key set, add to length.
- */
- if (cmi->Flags & COMMSEQ) len += COMMWIDTH + MAINWID;
-
- /* If this is a checkmark item, shift the text over to
- * make room and add that to the length.
- * Compute the max length.
- */
- if (cmi->Flags & CHECKIT) {
- itext->LeftEdge += CHECKWIDTH;
- len += CHECKWIDTH;
- }
- if (len > max) max = len;
- itext ++;
- }
-
- /* Secondary assignment loop. Position the text MenuItems and
- * init the horizontal lines.
- */
- for (i = 0; i < n; i++) {
- cmi = &mi[i];
-
- if (*nmen->str[i] != '-') {
- cmi->LeftEdge = x;
- cmi->TopEdge = y;
- cmi->Width = max;
- cmi->Height = ta.ta_YSize + TEXTGAP;
- y += cmi->Height;
- } else {
-
- /* Rule items point to their Image structure
- * and are just a little narrower than the
- * menu itself.
- */
- *img = generic_hrule;
- img->Width = max - RULEMARGIN;
- *cmi = generic_hitem;
- cmi->TopEdge = y + RULEGAP/2;
- y += RULEHEIGHT + RULEGAP;
- cmi->ItemFill = (APTR) img;
- img ++;
- }
- cmi->NextItem = cmi + 1;
- }
- mi[n - 1].NextItem = NULL;
-
- /* Attach submenu's, if any.
- */
- if (!(sub = nmen->submenu)) return mi;
-
- /* Use "max" as a flag for the success of the attachments.
- */
- max = 1;
- for (i = 0; i < n; i++) {
- if (*nmen->str[i] == '-') continue;
- if (sub->str)
- max &= AttachSubMenu (&mi[i], sub);
- sub ++;
- }
- if (!max) {
- FreeMItem (mi);
- return NULL;
- }
- return mi;
- }
-
-
- /*
- * Free a Menu structure created by GenStrip() that has items
- * created with GenItems().
- */
- void FreeMenu (mm)
- struct Menu *mm;
- {
- register short i;
- register struct Menu *t;
-
- i = 0;
- for (t = mm; t; t = t->NextMenu) {
- if (t->FirstItem) FreeMItem (t->FirstItem);
- i++;
- }
- FREI_N (mm, i);
- }
-
-
- /*
- * Free a MenuItem structure created by GenItems().
- */
- void FreeMItem (mi)
- register struct MenuItem *mi;
- {
- register short nit, nimg;
- register struct MenuItem *c;
- register struct IntuiText *it = NULL, *it1;
- struct Image *img = NULL;
-
- /* Scan the MenuItem structures and count the number of images
- * and IntuiText structures. Find the pointer to the first of
- * each structure in the set. That will be the first element
- * in the array that was allocated as a unit.
- */
- nit = nimg = 0;
- for (c = mi; c; c = c->NextItem) {
- if (c->SubItem) FreeMItem (c->SubItem);
- if (c->Flags & ITEMTEXT) {
- it1 = (struct IntuiText *) c->ItemFill;
- if (!it) it = it1;
- nit++;
-
- /* Free the subitem marker, if any.
- */
- if (it1->NextText) FREI (it1->NextText);
- } else {
- if (!img) img = (struct Image *) c->ItemFill;
- nimg++;
- }
- }
-
- /* Free the arrays of structures of images and texts, as
- * well as the main array of MenuItem structures themselves.
- */
- if (nit) FREI_N (it, nit);
- if (nimg) FREI_N (img, nimg);
- FREI_N (mi, nit + nimg);
- }
-