home *** CD-ROM | disk | FTP | other *** search
/ Club Amiga de Montreal - CAM / CAM_CD_1.iso / files / 230.lha / QuickMenu / qmenu.c < prev    next >
C/C++ Source or Header  |  1989-04-04  |  19KB  |  546 lines

  1. /*
  2.  * Quick Menu Package -- An easy way to make simple (but nice) menus.
  3.  *
  4.  * The client programmer will generally use the GenMenu()/FreeMenu() 
  5.  * interface which creates and frees a whole menu strip.  A menu strip
  6.  * -- both the main strip and menu item strips -- is defined by a
  7.  * NewMenu struct.  Each NewMenu struct (defined in qmenu.h) is just a
  8.  * pointer to an array of strings and a pointer to an array of
  9.  * NewMenu's.  The elements of the array of NewMenu's are paired with the
  10.  * strings in the array and represent the sub menus for that string.
  11.  * The string array is terminated with a null pointer.
  12.  *
  13.  * The package tries to do nice things, like automatically formatting
  14.  * the menu strings into blocks with their command keys, and placing
  15.  * markers pointing to subitem stips.  The strings for the menu items
  16.  * can contain special control codes to control the optional 
  17.  * characteristics of items.  Special codes come at the front of item
  18.  * strings and are delimited with a special character (SPECIAL,
  19.  * defined as '!' by default) and are listed briefly below:
  20.  *
  21.  *      c       Checkmark item
  22.  *      t       Checkmark toggle
  23.  *      +       Checkmark checked
  24.  *      b       Highlight box
  25.  *      n       Highlight none
  26.  *      d       Disabled
  27.  *      =C      Set command key to "C"
  28.  *      0101... Set item exclude mask
  29.  *
  30.  * Any number of 1's and 0's after the special character will set the
  31.  * Mutual exclude bits for that item in the order they occur.  So the
  32.  * first 1 or 0 will set bit 0 of the exclude mask, the second will
  33.  * set bit 1, etc.  Any unset will be 0.
  34.  *
  35.  * Additionally, if the item string is "-", the item will be 
  36.  * a non-selectable horizontal line (a "rule") that can be used to 
  37.  * visually group similar items in the menu strip.  There only need
  38.  * to be NewMenu structs in the array for text menu items, not rules,
  39.  * but don't forget to account for rules when counting MenuItem's
  40.  * since they take a slot.
  41.  *
  42.  *
  43.  * GenMenu() takes a pointer to a NewMenu struct and will create a main
  44.  * Menu strip from the strings in the struct, with menu items from the
  45.  * associated NewMenu's.  It returns a pointer to a Menu struct which is
  46.  * the first element in the list for this strip.
  47.  *
  48.  * GenStrip() takes a pointer to an array of strings and creates a
  49.  * top-level set of Menu structs for the main menu strip.
  50.  *
  51.  * FreeMenu() frees a Menu strip created with either GenMenu() or
  52.  * GenStrip().
  53.  *
  54.  * GenItems() takes a pointer to a NewMenu struct and creates a single
  55.  * list of MenuItem's from the description.  It also takes an X and Y
  56.  * offset for all the elements in the list.
  57.  *
  58.  * FreeMItem() frees structures created by the above function.
  59.  *
  60.  * AttachSubMenu() takes a single MenuItem and a NewMenu struct and
  61.  * attaches the MenuItems created from that NewMenu struct to the
  62.  * parent MenuItem.  The parent should also have been created with
  63.  * GenItems() in order to work correctly.  AttachSubMenu() places a
  64.  * little maker on the parent item to show that it has subitems.  It
  65.  * returns 1 for sucess and 0 for failure.
  66.  *
  67.  * -- WARNING:
  68.  *
  69.  * The client programmer is completely responsible for keeping the
  70.  * menus within the bounds of the screen and for keeping within the
  71.  * limits of Intuition.  The menu structures will not adjust as they
  72.  * hit the borders of the screen (which might be a nice enhancement).
  73.  * The strings in the NewMenu structures cannot be changed or deleted
  74.  * while the Menus are in use, although they can be used to create
  75.  * multiple Menus from the same strings.
  76.  *
  77.  **
  78.  * This code can be used and modified freely.  Do let me know of any
  79.  * improvements or enhancements you may add, and be sure to give credit
  80.  * where credit is due (in the comments) if you redistribute this code.
  81.  *
  82.  *      Stuart Ferguson         1/89
  83.  *      (shf@well.UUCP)
  84.  */
  85. #include <intuition/intuition.h>
  86. #include "qmenu.h"
  87.  
  88. /*
  89.  * Some useful macros for allocating and freeing structures and
  90.  * arrays of structures.
  91.  */
  92. #define NEW(typ)        (typ*)AllocMem((long)sizeof(typ),0L)
  93. #define FREI(p)         FreeMem(p,(long)sizeof(*p))
  94.  
  95. #define NEW_N(typ,n)    (typ*)AllocMem((long)((n)*sizeof(typ)),0L)
  96. #define FREI_N(p,n)     FreeMem(p,(long)((n)*sizeof(*p)))
  97.  
  98. char * AllocMem();
  99.  
  100. /*
  101.  * Definitions for formatting the menus.  Glossary:
  102.  *
  103.  *      TEXTCOLOR       - color of the text items in the menus.
  104.  *      MARKCOLOR       - color of the subitem marker.
  105.  *      RULECOLOR       - color of the horizontal lines in the menus.
  106.  *      RULEHEIGHT      - vertical thickness of the horizontal rules.
  107.  *      RULEGAP         - vertical blank space around the rules.
  108.  *      RULEMARGIN      - horizontal blank space around the rules.
  109.  *      TEXTGAP         - vertical blank space around the text items.
  110.  *      TEXTMARGIN      - horizontal blank space around the text items.
  111.  *      MAINWID         - width of the text font on the main menu.
  112.  *      MAINGAP         - spacing between items on the main menu strip.
  113.  *      SUBINDENT       - overlap between items and their subitems.
  114.  *      SUBDOWN         - vertical shift between items and their subitems.
  115.  */
  116. #define TEXTCOLOR       2
  117. #define MARKCOLOR       3
  118. #define RULECOLOR       0
  119. #define RULEHEIGHT      2
  120. #define RULEGAP         4
  121. #define RULEMARGIN      10
  122. #define TEXTGAP         2
  123. #define TEXTMARGIN      4
  124. #define MAINWID         10
  125. #define MAINGAP         10
  126. #define SUBINDENT       18
  127. #define SUBDOWN         0
  128.  
  129. /*
  130.  * Escape character for special control codes in text items strings.
  131.  */
  132. #define SPECIAL         '!'
  133.  
  134. /*
  135.  * The extern "ta" is set by the client program for
  136.  * the font to use for these menus.
  137.  */
  138. extern struct TextAttr ta;
  139.  
  140. /*
  141.  * Generic templates to use for creating the dynamic menu structures. 
  142.  */
  143. static struct IntuiText
  144.   generic_itext = {TEXTCOLOR, 0, JAM1, TEXTMARGIN/2, TEXTGAP/2, &ta,NULL,NULL},
  145.   generic_smark = {MARKCOLOR, 0, JAM1, 0, TEXTGAP/2, &ta, (UBYTE *) ";", NULL};
  146.  
  147. static struct Menu
  148.   generic_main = {NULL, 0, 0, 0, 10, MENUENABLED, NULL, NULL};
  149.  
  150. static struct MenuItem
  151.   generic_mitem = {
  152.          NULL, 0, 0, 0, 0,
  153.          ITEMTEXT | ITEMENABLED | HIGHCOMP,
  154.          0, NULL, NULL, 0, NULL, 0
  155. };
  156.  
  157. /* Image struct with no imagery for the horizontal lines.
  158.  */
  159. static struct Image
  160.   generic_hrule = {0, 0, 1, RULEHEIGHT, 2, NULL, 0, RULECOLOR, NULL};
  161.  
  162. static struct MenuItem
  163.   generic_hitem = {
  164.          NULL, RULEMARGIN/2, 0, 0, 0,
  165.          ITEMENABLED | HIGHNONE,
  166.          0, NULL, NULL, 0, NULL, 0
  167. };
  168.  
  169.  
  170. /*
  171.  * Takes an array of strings and associated array of NewMenu structs
  172.  * (as a single NewMenu struct) and constructs a menu strip from the
  173.  * descripton.  This is the main high-level call that most clients
  174.  * will make.
  175.  */
  176. struct Menu * GenMenu (nmen)
  177.         register struct NewMenu *nmen;
  178. {
  179.         register short  i, ok, n;
  180.         register struct Menu *mm;
  181.         register struct MenuItem *mi;
  182.  
  183.         /* Count menus to be generated and create top level structure.
  184.          */
  185.         for (n = 0; nmen->str[n]; n++) ;
  186.         mm = GenStrip (nmen->str);
  187.         if (!mm) return NULL;
  188.  
  189.         /* Create the item strip for each main menu and attach to the
  190.          * top level Menu structure.  Any failure causes the whole
  191.          * thing to crumble to dust.
  192.          */
  193.         ok = 1;
  194.         nmen = nmen->submenu;
  195.         for (i = 0; i < n; i++) {
  196.                 if (nmen->str) {
  197.                         mi = GenItems (nmen, 0L, 0L);
  198.                         mm[i].FirstItem = mi;
  199.                         ok &= (mi != NULL);
  200.                 }
  201.                 nmen ++;
  202.         }
  203.         if (!ok) {
  204.                 FreeMenu (mm);
  205.                 return NULL;
  206.         }
  207.         return mm;
  208. }
  209.  
  210.  
  211. /*
  212.  * Generate a menu strip.  Just creates the top level Menu structures,
  213.  * linked together and intialized.  Takes an array of pointers to
  214.  * strings.
  215.  */
  216. struct Menu * GenStrip (str)
  217.         char **str;
  218. {
  219.         register short  i, x, num;
  220.         register struct Menu *mm, *m;
  221.  
  222.         /*
  223.          * Create enough struct Menu's for the menu strip. 
  224.          */
  225.         for (num = 0; str[num]; num++) ;
  226.         mm = NEW_N (struct Menu, num);
  227.         if (!mm) return NULL;
  228.  
  229.         /*
  230.          * Init the structures using the generic Menu struct as a template.
  231.          * NOTE: the size of the font for these item labels is unknown for
  232.          *       windows on the Workbench screen, so a size should be used
  233.          *       that will work with 60 & 80 column fonts. 
  234.          */
  235.         x = 0;
  236.         for (i = 0; i < num; i++) {
  237.                 m = &mm[i];
  238.                 *m = generic_main;
  239.                 m->LeftEdge = x;
  240.                 m->Width = strlen (str[i]) * MAINWID + TEXTMARGIN;
  241.                 x += m->Width + MAINGAP;
  242.                 m->MenuName = str[i];
  243.                 m->NextMenu = m + 1;
  244.         }
  245.         mm[num - 1].NextMenu = NULL;
  246.         return mm;
  247. }
  248.  
  249.  
  250. /*
  251.  * Attach a submenu to a MenuItem.  Takes the parent MenuItem and a
  252.  * NewMenu structure that will be attached as a sub-menu.  
  253.  * Attaches a ";" mark at the end of the parent item and calls
  254.  * GenItems() to create the sub-menu strip.
  255.  */
  256. BOOL AttachSubMenu (mi, nmen)
  257.         register struct MenuItem        *mi;
  258.         struct NewMenu                  *nmen;
  259. {
  260.         register struct IntuiText *it;
  261.  
  262.         /* Create an IntuiText with the marker and position it
  263.          * at the right edge of the item.
  264.          */
  265.         if (!(it = NEW (struct IntuiText))) return FALSE;
  266.         *it = generic_smark;
  267.         it->LeftEdge = mi->Width - IntuiTextLength (it) - 2;
  268.  
  269.         /* Create the subitem structure and attach to the main item.
  270.          */
  271.         if (!(mi->SubItem = GenItems
  272.               (nmen, (LONG) (mi->Width - SUBINDENT), (LONG) SUBDOWN))) {
  273.                 FREI (it);
  274.                 return FALSE;
  275.         }
  276.  
  277.         /* Only if it all worked attach the new text structure.
  278.          */
  279.         ((struct IntuiText *) mi->ItemFill)->NextText = it;
  280.         return TRUE;
  281. }
  282.  
  283.  
  284. /*
  285.  * Takes the given menu text item and skips past the special control
  286.  * codes while adjusting the associated MenuItem appropriately.
  287.  * Special codes are in the form of "!x", where "x" is:
  288.  *
  289.  *      c       Checkmark item
  290.  *      t       Checkmark toggle
  291.  *      b       Highlight box
  292.  *      n       Highlight none
  293.  *      d       Disabled
  294.  *      +       Checkmark checked
  295.  *      =C      Set command key to "C"
  296.  *      1010... Set item exclude mask
  297.  *
  298.  * Takes the mostly defined MenuItem and diddles it.  Returns a pointer
  299.  * to the item text with control codes stripped.
  300.  */
  301. static UBYTE * ProcessSpecialStuff (str, mi)
  302.         char *str;
  303.         struct MenuItem *mi;
  304. {
  305.         register LONG   x;
  306.         register int    i;
  307.  
  308.         while (*str == SPECIAL) {
  309.                 switch (*++str) {
  310.                     case 'c':
  311.                         mi->Flags |= CHECKIT;
  312.                         break;
  313.                     case 't':
  314.                         mi->Flags |= CHECKIT | MENUTOGGLE;
  315.                         break;
  316.                     case 'b':
  317.                         mi->Flags &= ~HIGHFLAGS;
  318.                         mi->Flags |= HIGHBOX;
  319.                         break;
  320.                     case 'n':
  321.                         mi->Flags &= ~HIGHFLAGS;
  322.                         mi->Flags |= HIGHNONE;
  323.                         break;
  324.                     case 'd':
  325.                         mi->Flags &= ~ITEMENABLED;
  326.                         break;
  327.                     case '+':
  328.                         mi->Flags |= CHECKIT | CHECKED;
  329.                         break;
  330.                     case '=':
  331.                         mi->Flags |= COMMSEQ;
  332.                         mi->Command = *++str;
  333.                         break;
  334.                     case '0':
  335.                     case '1':
  336.                         x = 0;
  337.                         i = 0;
  338.                         while (*str == '0' || *str == '1')
  339.                                 x += (*str++ - '0') << (i++);
  340.                         mi->MutualExclude = x;
  341.                         str--;
  342.                         break;
  343.                 }
  344.                 str++;
  345.         }
  346.         return (UBYTE*) str;
  347. }
  348.  
  349.  
  350. /*
  351.  * Construct a basic item list for a menu.  Takes a NewMenu structure
  352.  * which contains a pointer to an array of pointers to strings and a 
  353.  * pointer to an array of NewMenu structures.  The strings contain the
  354.  * item text for each menu plus optional special control codes.  If the
  355.  * string is "-", the item will be a horizontal rule rather than a text
  356.  * item.  The NewMenu structures, if not NULL, are the sub-menu's for
  357.  * each menu in the array.
  358.  * "x" and "y" are the horizontal and vertical offsets of this set of
  359.  * MenuItems.  These are set by AttachSubMenu() for positioning submenus
  360.  * under their parent items.
  361.  */
  362. struct MenuItem *GenItems (nmen, x, y)
  363.         struct NewMenu *nmen;
  364.         LONG    x, y;
  365. {
  366.         register struct MenuItem *mi, *cmi;
  367.         register struct IntuiText *itext;
  368.         struct Image   *img;
  369.         register short  i, len, max;
  370.         short           n;
  371.         struct NewMenu *sub;
  372.  
  373.         /* Count menu items (n) and allocate an array for the strip.
  374.          */
  375.         for (n = 0; nmen->str[n]; n++) ;
  376.         if (!(mi = NEW_N (struct MenuItem, n))) return NULL;
  377.  
  378.         /* Counts the number of rules in the menu ("-" strings)
  379.          * and allocates the structures for the lines and the text items.
  380.          */
  381.         max = 0;
  382.         for (i = 0; i < n; i++) max += (*nmen->str[i] == '-');
  383.         if (n - max)
  384.                 if (!(itext = NEW_N (struct IntuiText, n - max))) {
  385.                         FREI_N (mi, n);
  386.                         return NULL;
  387.                 }
  388.         if (max)
  389.                 if (!(img = NEW_N (struct Image, max))) {
  390.                         FREI_N (mi, n);
  391.                         if (n - max) FREI_N (itext, n - max);
  392.                         return NULL;
  393.                 }
  394.  
  395.         /* Loop through text menu items and initialize the
  396.          * associated IntuiText structures.  Compute the maximum
  397.          * width of the menu taking command keys into account while
  398.          * assigning all the other parts of the text MenuItem's.
  399.          */
  400.         max = 0;
  401.         for (i = 0; i < n; i++) {
  402.                 if (*nmen->str[i] == '-') continue;     /* skip rules */
  403.  
  404.                 /* Init the text MenuItem to point to the assocd IntuiText.
  405.                  */
  406.                 cmi = &mi[i];
  407.                 *cmi = generic_mitem;
  408.                 cmi->ItemFill = (APTR) itext;
  409.  
  410.                 /* Init the IntuiText and adjust the MenuItem from the
  411.                  * flags set in the menu text string.
  412.                  */
  413.                 *itext = generic_itext;
  414.                 itext->IText = ProcessSpecialStuff (nmen->str[i], cmi);
  415.  
  416.                 /* Make a first cut at measuring the length of the item.
  417.                  */
  418.                 len = IntuiTextLength (itext) + TEXTMARGIN;
  419.  
  420.                 /* If command key set, add to length.
  421.                  */
  422.                 if (cmi->Flags & COMMSEQ) len += COMMWIDTH + MAINWID;
  423.  
  424.                 /* If this is a checkmark item, shift the text over to
  425.                  * make room and add that to the length.
  426.                  * Compute the max length.
  427.                  */
  428.                 if (cmi->Flags & CHECKIT) {
  429.                         itext->LeftEdge += CHECKWIDTH;
  430.                         len += CHECKWIDTH;
  431.                 }
  432.                 if (len > max) max = len;
  433.                 itext ++;
  434.         }
  435.  
  436.         /* Secondary assignment loop.  Position the text MenuItems and
  437.          * init the horizontal lines.
  438.          */
  439.         for (i = 0; i < n; i++) {
  440.                 cmi = &mi[i];
  441.  
  442.                 if (*nmen->str[i] != '-') {
  443.                         cmi->LeftEdge = x;
  444.                         cmi->TopEdge = y;
  445.                         cmi->Width = max;
  446.                         cmi->Height = ta.ta_YSize + TEXTGAP;
  447.                         y += cmi->Height;
  448.                 } else {
  449.  
  450.                         /* Rule items point to their Image structure
  451.                          * and are just a little narrower than the
  452.                          * menu itself.
  453.                          */
  454.                         *img = generic_hrule;
  455.                         img->Width = max - RULEMARGIN;
  456.                         *cmi = generic_hitem;
  457.                         cmi->TopEdge = y + RULEGAP/2;
  458.                         y += RULEHEIGHT + RULEGAP;
  459.                         cmi->ItemFill = (APTR) img;
  460.                         img ++;
  461.                 }
  462.                 cmi->NextItem = cmi + 1;
  463.         }
  464.         mi[n - 1].NextItem = NULL;
  465.  
  466.         /* Attach submenu's, if any.
  467.          */
  468.         if (!(sub = nmen->submenu)) return mi;
  469.  
  470.         /* Use "max" as a flag for the success of the attachments.
  471.          */
  472.         max = 1;
  473.         for (i = 0; i < n; i++) {
  474.                 if (*nmen->str[i] == '-') continue;
  475.                 if (sub->str)
  476.                         max &= AttachSubMenu (&mi[i], sub);
  477.                 sub ++;
  478.         }
  479.         if (!max) {
  480.                 FreeMItem (mi);
  481.                 return NULL;
  482.         }
  483.         return mi;
  484. }
  485.  
  486.  
  487. /*
  488.  * Free a Menu structure created by GenStrip() that has items 
  489.  * created with GenItems().
  490.  */
  491. void FreeMenu (mm)
  492.         struct Menu    *mm;
  493. {
  494.         register short  i;
  495.         register struct Menu *t;
  496.  
  497.         i = 0;
  498.         for (t = mm; t; t = t->NextMenu) {
  499.                 if (t->FirstItem) FreeMItem (t->FirstItem);
  500.                 i++;
  501.         }
  502.         FREI_N (mm, i);
  503. }
  504.  
  505.  
  506. /*
  507.  * Free a MenuItem structure created by GenItems().
  508.  */
  509. void FreeMItem (mi)
  510.         register struct MenuItem *mi;
  511. {
  512.         register short  nit, nimg;
  513.         register struct MenuItem *c;
  514.         register struct IntuiText *it = NULL, *it1;
  515.         struct Image   *img = NULL;
  516.  
  517.         /* Scan the MenuItem structures and count the number of images
  518.          * and IntuiText structures.  Find the pointer to the first of
  519.          * each structure in the set.  That will be the first element
  520.          * in the array that was allocated as a unit.
  521.          */
  522.         nit = nimg = 0;
  523.         for (c = mi; c; c = c->NextItem) {
  524.                 if (c->SubItem) FreeMItem (c->SubItem);
  525.                 if (c->Flags & ITEMTEXT) {
  526.                         it1 = (struct IntuiText *) c->ItemFill;
  527.                         if (!it) it = it1;
  528.                         nit++;
  529.  
  530.                         /* Free the subitem marker, if any.
  531.                          */
  532.                         if (it1->NextText) FREI (it1->NextText);
  533.                 } else {
  534.                         if (!img) img = (struct Image *) c->ItemFill;
  535.                         nimg++;
  536.                 }
  537.         }
  538.  
  539.         /* Free the arrays of structures of images and texts, as
  540.          * well as the main array of MenuItem structures themselves.
  541.          */
  542.         if (nit) FREI_N (it, nit);
  543.         if (nimg) FREI_N (img, nimg);
  544.         FREI_N (mi, nit + nimg);
  545. }
  546.