home *** CD-ROM | disk | FTP | other *** search
/ ARM Club 1 / ARM_CLUB_CD.iso / contents / apps / clib / progs / timslib / TimsLib / c / Menu < prev    next >
Encoding:
Text File  |  1993-01-21  |  26.7 KB  |  942 lines

  1. /*
  2.   Menu.c
  3. */
  4. /*                           
  5.  
  6.   #####                 #         #
  7.     #    #              #      #  #
  8.     #                   #         #
  9.     #   ##  ####   #### #     ##  ####
  10.     #    #  # # # #     #      #  #   #
  11.     #    #  # # #  ###  #      #  #   #
  12.     #    #  # # #     # #      #  #   #
  13.     #   ### # # # ####  ##### ### ####
  14.  
  15. -----------------------------------------------------------------------------
  16.  
  17. This is source for use with the 'DeskLib' Wimp C programming library for
  18. Risc OS. I currently use v1.04 of DeskLib.  This source is FreeWare, which
  19. means you can use it to write commercial applications, but you may not charge
  20. *in any way* for the distribution of this source.  I (Tim Browse) retain
  21. all copyright on this source.
  22.  
  23. This source is provided 'as is' and I offer no guarantees that is useful,
  24. bug-free, commented, that it will compile, or even that it exists.
  25.  
  26. If it breaks in half, you own both pieces.
  27.  
  28. All source © Tim Browse 1993
  29.  
  30. -----------------------------------------------------------------------------
  31.  
  32. */
  33.  
  34. /* ANSI includes */
  35. #include "stdlib.h"
  36. #include "string.h"
  37.  
  38. /* DeskLib includes */
  39. #include "DeskLib.Core.h"
  40. #include "DeskLib.Wimp.h"
  41. #include "DeskLib.Event.h"
  42. #include "DeskLib.EventMsg.h"
  43. #include "DeskLib.WimpSWIs.h"
  44. #include "DeskLib.Error.h"
  45. #include "DeskLib.LinkList.h"
  46.  
  47. /* TimsLib includes */
  48. #include "lib.h"
  49. #include "IconLib.h"
  50.  
  51. #include "Menu.h"
  52.  
  53. /*
  54.    General Note:
  55.  
  56.    The typedef 'menu' is actually a wimp_menuptr, but cannot be used with
  57.    free, as the word before the wimp_menublock points to the parent of this
  58.    menu (if any), and the memory is allocated in one go (see typedef below).
  59.    Therefore to free storage you need:
  60.    free(((char *) menu) - 4);
  61.    i.e. don't bother, it's easier to use Menu_Dispose().
  62. */
  63.  
  64. typedef struct
  65. {
  66.   menu_ptr   parent;
  67.   menu_block menublock;
  68. } menu_info;
  69.  
  70. BOOL menu_adjust_pressed = FALSE;
  71.  
  72. window_handle menu_current_window = event_ANY;
  73. icon_handle   menu_current_icon   = event_ANY;
  74.  
  75. static void Menu__InitItem(menu_item *item)
  76. {
  77.   static menu_item default_item;
  78.  
  79.   static BOOL first_time = TRUE;
  80.  
  81.   if (first_time)
  82.   {
  83.     first_time = FALSE;
  84.  
  85.     /* Initialise the menu flags */
  86.     default_item.menuflags.value = 0;
  87.  
  88.     /* Initialise the menu item to have no sub-menu */
  89.     default_item.submenu.value = -1;
  90.  
  91.     /* Initialise the icon flags */
  92.     default_item.iconflags.value = icon_TEXT | icon_FILLED;
  93.     default_item.iconflags.data.foreground = colour_BLACK;
  94.     default_item.iconflags.data.background = colour_WHITE;
  95.  
  96.     /* Initialise the icon data */
  97.     default_item.icondata.text[0] = '\0';
  98.   }
  99.  
  100.   memcpy(item, &default_item, sizeof(menu_item));
  101. }
  102.  
  103.  
  104. static void Menu__SetOption(menu_item *item, char opt)
  105. {
  106.   switch (opt)
  107.   {
  108.     case '!':  /* Tick this entry */
  109.       item->menuflags.data.ticked = TRUE;
  110.       break;
  111.  
  112.     case '~':  /* Fade this entry */
  113.       item->iconflags.data.shaded = TRUE;
  114.       break;
  115.  
  116.     case '=':  /* Add dotted line after this entry */
  117.       item->menuflags.data.dotted = TRUE;
  118.       break;
  119.  
  120.     case '>':  /* This entry has a dialogue box as a sub-menu */
  121.       item->menuflags.data.notifysub = TRUE;
  122.  
  123.       /* Set submenu flag - use a legal window handle, but not likely to be in use */
  124.       item->submenu.window = (window_handle) 0x7FFF; 
  125.       break;
  126.  
  127.     /* Ignore space */
  128.   }
  129. }
  130.  
  131. #define is_sep(c) (((c) == ',') || ((c) == '|'))
  132. #define is_opt(c) (((c) == '!') || ((c) == '>') || ((c) == '~') || ((c) == '=') || ((c) == ' '))
  133.  
  134. static int Menu__ReadItemName(char *item_name, char *src)
  135. {
  136.   int i = 0;
  137.  
  138.   /* A name can start with any character other than a 'sep' or an 'opt', and can
  139.      then contain any character other than a 'sep'.
  140.   */
  141.   if (!is_sep(src[0]) && !is_opt(src[0]))
  142.   {
  143.     do
  144.     {
  145.       /* Copy this character and move on */
  146.       item_name[i] = src[i];
  147.       i++;
  148.     } while (!is_sep(src[i]) && (src[i] != '\0'));
  149.   }
  150.  
  151.   /* Terminate the item name */
  152.   item_name[i] = '\0';
  153.  
  154.   /* Tell the caller how many characters we used */
  155.   return i;  
  156. }
  157.  
  158.  
  159. #define Menu__FirstEntry(menu) ((menu_item *) (((menu_ptr) (menu) + 1)))
  160.  
  161. menu Menu_New(char *name, char *desc)
  162. {
  163.   int entries = 1,
  164.       i = 0,
  165.       name_len,
  166.       max_name_len = strlen(name); /* In case title is longest string */
  167.  
  168.   char item_name[80];
  169.  
  170.   menu_info *the_menu;
  171.   menu_item *item;
  172.  
  173.   /* Exit quietly if no description string */
  174.   if (desc == NULL)
  175.     return NULL;
  176.  
  177.   /* Find out how many entries are needed */
  178.   while (desc[i] != '\0')
  179.   {
  180.     if ((desc[i] == ',') || (desc[i] == '|'))
  181.       entries++;
  182.     i++;
  183.   }
  184.  
  185.   /* Get the memory for these entries */
  186.   the_menu = (menu_info *) malloc(sizeof(menu_info) + (entries * sizeof(menu_item)));
  187.  
  188.   /* Get pointer to first item, using some obscure pointer arithmetic */
  189.   item = Menu__FirstEntry(&the_menu->menublock);
  190.  
  191.   i = 0;
  192.  
  193.   /* Construct the menu entries */
  194.   while (desc[i] != '\0')
  195.   {
  196.     /* Initialise this menu item */
  197.     Menu__InitItem(item);
  198.  
  199.     /* Look for an option */
  200.     if (is_opt(desc[i]))
  201.     {
  202.       Menu__SetOption(item, desc[i]);
  203.       i++;
  204.     }
  205.  
  206.     /* Read the item name */
  207.     name_len = Menu__ReadItemName(item_name, desc + i);
  208.  
  209.     /* Copy the item name into the menu icon */
  210.  
  211.     if (name_len <= wimp_MAXNAME)
  212.     {
  213.       /* normal non-indirected icon */
  214.       strncpy(item->icondata.text, item_name, wimp_MAXNAME);
  215.     }
  216.     else
  217.     {
  218.       /* Too long - make into indirected icon */
  219.  
  220.       item->iconflags.data.indirected = TRUE;
  221.       item->icondata.indirecttext.buffer = save_str(item_name, 0);
  222.  
  223.       item->icondata.indirecttext.bufflen = name_len + 1;    /* for terminator */
  224.  
  225.       item->icondata.indirecttext.validstring = (char *) -1; /* no validation  */
  226.     }
  227.  
  228.     /* Check if this is the longest item so far */
  229.     if (name_len > max_name_len)
  230.       max_name_len = name_len;
  231.  
  232.     /* Move to next menu item */
  233.     i += name_len;
  234.  
  235.     /* Skip the separator, if there is one */
  236.     if (is_sep(desc[i]))
  237.       i++;
  238.  
  239.     item++;
  240.   }
  241.  
  242.   /* Set the 'last' flag on the last menu item */
  243.   item--;
  244.   item->menuflags.data.last = TRUE;
  245.  
  246.   /* Set up menu block */
  247.   strncpy(the_menu->menublock.title, name, wimp_MAXNAME);
  248.  
  249.   /* These attributes taken from Wimp Application notes (RO2 PRMS p1314) */
  250.   the_menu->menublock.titlefore = colour_BLACK;
  251.   the_menu->menublock.titleback = colour_GREY2;
  252.   the_menu->menublock.workfore  = colour_BLACK;
  253.   the_menu->menublock.workback  = colour_WHITE;
  254.   the_menu->menublock.width     = (max_name_len + 1) * 16; /* !!!Find a legal way!!! */
  255.   the_menu->menublock.height    = 44;
  256.   the_menu->menublock.gap       = 0;
  257.  
  258.   return (menu) &(the_menu->menublock);
  259. }
  260.  
  261. menu Menu_ColourMenu(char *title, char *last_item)
  262. {
  263.   menu colour_menu;
  264.   menu_item *item;
  265.   int i;
  266.   char menu_desc[100]; /* Should be enough for really stoopid 'last_item' params */
  267.  
  268.   strcpy(menu_desc, "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,");
  269.  
  270.   if (last_item != NULL)
  271.     /* Add a separator before last_item */
  272.     strcat(menu_desc, "=");
  273.  
  274.   strcat(menu_desc, "15");
  275.  
  276.   if (last_item != NULL)
  277.   {
  278.    strcat(menu_desc, ",");
  279.    strcat(menu_desc, last_item);
  280.   }
  281.  
  282.   /* Create the menu */  
  283.   colour_menu = Menu_New(title, menu_desc);
  284.  
  285.   item = Menu__FirstEntry(colour_menu);
  286.  
  287.   /* Set the background colours of the menu items */
  288.   for (i = 0; i <= 15; i++)
  289.     item[i].iconflags.data.background = i;
  290.  
  291.   /* Set the foreground colours (always black or white) */
  292.   for (i = 0; i <= 15; i++)
  293.     if ((i <= 3) || (i == 9) || (i == 12) || (i >= 14))
  294.       item[i].iconflags.data.foreground = colour_BLACK;
  295.     else
  296.       item[i].iconflags.data.foreground = colour_WHITE;
  297.  
  298.   return colour_menu;
  299. }
  300.  
  301.  
  302. void Menu__Dispose(menu_block *the_menu, BOOL recursive)
  303. {
  304.   menu_info *info;
  305.  
  306.   /* Get pointer to the first item */
  307.   menu_item *item = (menu_item *) (the_menu + 1);
  308.  
  309.   /* De-allocate any indirect data / sub-menus */
  310.   do
  311.   {
  312.     /* Does this item have a sub-menu (but not a dialogue box sub-menu) */
  313.     if (recursive && item->submenu.value >= 0x8000)
  314.       Menu__Dispose(item->submenu.menu, recursive);
  315.  
  316.     /* Free up any indirected data */
  317.     IconLib_DisposeIndData(&item->icondata, item->iconflags);
  318.  
  319.     /* Move to next item */
  320.     item++;
  321.   } while (item->menuflags.data.last == FALSE);
  322.  
  323.   /* Free the data for the menu block - this includes all the menu items, and
  324.      also the word preceeding the menu_block, as it points to the parent menu.
  325.   */
  326.   info = (menu_info *) (((char *) the_menu) - 4);
  327.   free(info);
  328. }
  329.  
  330. void Menu_Dispose(menu the_menu, BOOL recursive)
  331. {
  332.   /* Just a veneer for the slightly lower-level Menu__Dispose() */
  333.   Menu__Dispose((menu_block *) the_menu, recursive);
  334. }
  335.  
  336. void Menu_SubMenu(menu parent, int entry, menu submenu)
  337. {
  338.   /* Get a pointer to the menu info block of the submenu */
  339.   menu_info *submenu_info = (menu_info *) (((int *) submenu) - 1);
  340.  
  341.   /* Get a pointer to the first entry */
  342.   menu_item *item = Menu__FirstEntry(parent);
  343.  
  344.   /* Plug the submenu into the specified menu entry */
  345.   item[entry].submenu.menu = (menu_ptr) submenu;
  346.  
  347.   /* Link the submenu to the parent */
  348.   submenu_info->parent = (menu_ptr) parent;
  349. }
  350.  
  351. void Menu_AddDialogBox(menu the_menu, int entry, window_handle window)
  352. {
  353.   /* Get a pointer to the first entry */
  354.   menu_item *item = Menu__FirstEntry(the_menu);
  355.  
  356.   /* Plug the submenu into the specified menu entry */
  357.   item[entry].submenu.window = window;
  358. }
  359.  
  360. void Menu_SetFlags(menu the_menu, int entry, BOOL tick, BOOL fade)
  361. {
  362.   /* Get a pointer to the first item */
  363.   menu_item *item = Menu__FirstEntry(the_menu);
  364.  
  365.   /* Set the flags as required */
  366.   item[entry].menuflags.data.ticked = tick;
  367.   item[entry].iconflags.data.shaded = fade;
  368. }
  369.  
  370. void Menu_MakeWritable(menu the_menu, int entry, 
  371.                         char *buffer, int bufflen, char *validstring)
  372. {
  373.   char *the_text = NULL; /* Used to keep a copy of the current text of the entry */
  374.  
  375.   /* Get a pointer to the first item */
  376.   menu_item *item = Menu__FirstEntry(the_menu);
  377.  
  378.   /* Move to the entry we want */
  379.   item += entry;
  380.  
  381.   /* If it's currently a text (or text & sprite) icon, save the text string */
  382.  
  383.   switch (ICON_TYPE(item->iconflags.value))
  384.   {
  385.     case TXT_ONLY:
  386.     case TXT_AND_SPR:
  387.       if (item->iconflags.data.indirected)
  388.       {
  389.         the_text = save_str(item->icondata.indirecttext.buffer, 0);
  390.       }
  391.       else
  392.       {
  393.         the_text = (char *) malloc(wimp_MAXNAME + 1);
  394.         strncpy(the_text, item->icondata.text, wimp_MAXNAME);
  395.         the_text[wimp_MAXNAME] = '\0';
  396.       }
  397.   }
  398.  
  399.   /* Free any indirected data */
  400.   IconLib_DisposeIndData(&item->icondata, item->iconflags);
  401.  
  402.   /* Make the entry into a writable, indirected, text-only icon */
  403.   item->menuflags.data.writable   = TRUE;
  404.   item->iconflags.data.text       = TRUE;
  405.   item->iconflags.data.sprite     = FALSE; /* just to be safe */
  406.   item->iconflags.data.indirected = TRUE;
  407.  
  408.   /* Plug in the buffer and validation string */
  409.   item->icondata.indirecttext.buffer      = buffer;
  410.   item->icondata.indirecttext.bufflen     = bufflen;
  411.   item->icondata.indirecttext.validstring = validstring;
  412.  
  413.   /* Copy the old text back in and free the buffer, if necessary */
  414.   if (the_text != NULL)
  415.   {
  416.     strncpy(buffer, the_text, bufflen - 1);
  417.     free(the_text);
  418.   }
  419. }
  420.  
  421. void Menu_Show(menu the_menu, wimp_point mouse_pos, BOOL iconbar)
  422. {
  423.   /* If an iconbar menu, position bottom of menu 96 OS units above bottom
  424.      of screen.
  425.   */
  426.   if (iconbar)
  427.   {
  428.     /* Title bar is not included in the positioning of menu windows */
  429.  
  430.     int menu_height = 0;
  431.  
  432.     menu_ptr menu = (menu_ptr) the_menu;
  433.     /* Get a pointer to the first item */
  434.     menu_item *item = Menu__FirstEntry(the_menu);
  435.  
  436.     BOOL not_finished = TRUE;
  437.  
  438.     /* Process each menu entry */
  439.  
  440.     while (not_finished)
  441.     {
  442.       /* Add height of menu item for each item */
  443.         menu_height += menu->height;
  444.  
  445.       /* Add extra height for a separator */
  446.       if (item->menuflags.value & 2)
  447.         /* NB this value may be wrong for non-standard item heights/inter-item gaps */
  448.         menu_height += (6 * 4);
  449.  
  450.       /* If not the last item, add the inter-item gap */
  451.       if (item->menuflags.data.last)
  452.       {
  453.         menu_height += menu->gap;
  454.         not_finished = FALSE;
  455.       }
  456.  
  457.       /* Process the next item */
  458.       item++;
  459.     }
  460.  
  461.     /* Position menu */
  462.     mouse_pos.y = 96 + menu_height;
  463.   }
  464.  
  465.   /* Open the menu 64 OS units to the left of the mouse click, as recommended
  466.      by the RO2 PRMS, p1314.
  467.   */
  468.   Error_Check(Wimp_CreateMenu((menu_block *) the_menu, mouse_pos.x - 64, mouse_pos.y));
  469. }
  470.  
  471.  
  472. /* Menu attachment routines */
  473.  
  474. typedef struct handler_str
  475. {
  476.   union
  477.   {
  478.     int value;
  479.     struct 
  480.     {
  481.       unsigned int menu_maker   : 1; /* 1 => data union contains a menu_maker       */
  482.       unsigned int icon_handler : 1; /* 1 => Menu should be shown if select pressed */
  483.       unsigned int iconbar      : 1; /* 1 => Menu is attached to icon bar icon      */
  484.     } data;
  485.   } flags;
  486.  
  487.   union
  488.   {
  489.     menu       menu;
  490.     menu_maker maker;
  491.   } data;
  492.  
  493.   menu_handler handler_proc;
  494.   void *reference;
  495. } handler_str;
  496.  
  497. typedef struct icon_node
  498. {
  499.   linklist_header header;
  500.   icon_handle     icon;
  501.   handler_str     handler;
  502. } icon_node;
  503.  
  504. typedef struct window_node
  505. {
  506.   linklist_header  header;
  507.   window_handle    window;       /* event_ANY means catch all menu events */
  508.   linklist_header  icons_anchor;
  509.   handler_str      handler;
  510. } window_node;
  511.  
  512.  
  513. /* Contains tree of menu handlers */
  514. linklist_header handlers = {NULL, NULL};
  515.  
  516. /*
  517.    'submenu_handle' is used to store the window handle passed to 
  518.    Menu_AddDynamicDialogBox, which should be called in response to receiving a 
  519.    menu warning (requested by using the '>' option in the menu description string).
  520.  
  521.    Sequence of events is:
  522.      Message menu warning arrives.
  523.      Menu__MessageHandler calls appropriate user menu handler
  524.      User's menu handler calls Menu_AddDynamicDialogBox with handle to add.
  525.      Menu_AddDynamicDialogBox stores window handle and returns.
  526.      User's menu handler returns.
  527.      Menu__MessageHandler calls SWI Wimp_CreateSubmenu with the saved window handle,
  528.       and returns.
  529.  
  530.    i.e., a bit neater than all that dbox malarkey with Risc OS Lib.
  531.          (and much simpler for user).
  532. */
  533.  
  534. static window_handle submenu_handle;
  535.  
  536.  
  537. void Menu_AddDynamicDialogBox(window_handle window)
  538. {
  539.   /* A bit simple - provide a macro instead? */
  540.   submenu_handle = window;
  541. }
  542.  
  543. /* BOOL Menu__MessageHandler()
  544.  
  545.    This handles menu warning messages, and calls the appropriate user menu
  546.    handler to get the window handle to use as a sub-menu.
  547.  
  548.    It passes on the selection array to the user's handler, just like a normal
  549.    menu selection.
  550. */
  551.  
  552. BOOL menu_selection = TRUE;
  553.  
  554. wimp_point menu_submenupos;
  555.  
  556. static BOOL Menu__MessageHandler(event_pollblock *event, void *reference)
  557. {
  558.   /* If this is a menu warning message, process it */
  559.   if (event->data.message.header.action == message_MENUWARNING)
  560.   {
  561.     handler_str *handler = (handler_str *) reference;
  562.  
  563.     /* Get the window handle for the submenu */
  564.  
  565.     /* Tell caller this is a sub-menu warning message event */
  566.     menu_selection = FALSE;
  567.  
  568.     /* Let them access submenu position if they want to */
  569.     menu_submenupos.x = event->data.message.data.words[1];
  570.     menu_submenupos.y = event->data.message.data.words[2];
  571.  
  572.     /* Initialise the submenu pointer/window handle */
  573.     submenu_handle = -1;
  574.  
  575.     handler->handler_proc(handler->reference, event->data.message.data.words+3);
  576.  
  577.     menu_selection = TRUE;
  578.  
  579.     /* Call Wimp_CreateSubMenu to show the submenu, if the handler created one */
  580.     if (submenu_handle != -1)
  581.       Error_Check(Wimp_CreateSubMenu((menu_block *) submenu_handle,
  582.                                      menu_submenupos.x, menu_submenupos.y));
  583.     return TRUE;
  584.   }
  585.   else
  586.     /* Pass on to next handler */
  587.     return FALSE;
  588. }
  589.  
  590.  
  591. /* The following variable is used because the Wimp doesn't tell us when a menu is
  592.    closed if a selection is not made from it (i.e. if the user clicks outside the
  593.    menu.  Therefore if menu events are to be claimed, and this variable is non-NULL,
  594.    then the handler should be de-installed before the new handlersare installed.
  595.    Otherwise the wrong handlers get called, as the Event module works on a first
  596.    registered, first served basis.
  597. */
  598. static handler_str *installed_handler = NULL;
  599.  
  600. /* BOOL Menu__SelectionHandler()
  601.  
  602.    This will pass on a selection array to the relevant handler.
  603.    If adjust is pressed, the menu is re-opened.
  604.  
  605.    NB. Menu selection events are released, unless adjust is pressed.
  606.        This is in case the user wants to use them at other times.
  607. */
  608. static BOOL Menu__SelectionHandler(event_pollblock *event, void *reference)
  609. {
  610.   handler_str *handler = (handler_str *) reference;
  611.  
  612.   /* Get pointer info */
  613.   mouse_block mouse;
  614.   Error_Check(Wimp_GetPointerInfo(&mouse));
  615.  
  616.   /* Get mouse button information */
  617.   if (mouse.button.data.adjust)
  618.     menu_adjust_pressed = TRUE;
  619.  
  620.   /* Deal with selection */
  621.   handler->handler_proc(handler->reference, event->data.selection);
  622.  
  623.   /* If adjust pressed, re-open the menu.
  624.      The position doesn't matter - the Wimp knows it's being re-opened and so it
  625.      works out the position for itself.
  626.   */
  627.   if (menu_adjust_pressed)
  628.   {
  629.     wimp_point pos;
  630.     menu m;
  631.  
  632.     /* Call the menu maker, if necessary */
  633.     if (handler->flags.data.menu_maker)
  634.       m = (handler->data.maker)(handler->reference);
  635.     else
  636.       m = handler->data.menu;
  637.  
  638.     pos.x = 0; pos.y = 0;
  639.  
  640.     Menu_Show(m, pos, FALSE);
  641.  
  642.     menu_adjust_pressed = FALSE;
  643.   }
  644.   else
  645.   {
  646.     /* Menu to be closed - release menu selection events... */
  647.     Event_Release(event_MENU, event_ANY, event_ANY, 
  648.                   Menu__SelectionHandler, reference);
  649.     EventMsg_Release(message_MENUWARNING, event_ANY, Menu__MessageHandler);
  650.     /* Ensure they aren't released again */
  651.     installed_handler = NULL;
  652.   }
  653.  
  654.   /* We always handle menu selection events */
  655.   return TRUE;
  656. }
  657.   
  658.  
  659. /* BOOL Menu__EventHandler()
  660.  
  661.    This is called when a menu might be requested (ie a mouse click on a window
  662.    or icon user has registered a menu for.
  663.    If a menu is needed, it is created if necessary, and then shown.
  664.    Menu selection events are also claimed for passing to Menu__SelectionHandler().
  665. */
  666.  
  667. static BOOL Menu__EventHandler(event_pollblock *poll_block, void *reference)
  668. {
  669.   handler_str *handler = (handler_str *) reference;
  670.   mouse_block *mouse   = &poll_block->data.mouse;
  671.  
  672.   /* If menu was pressed, or if select was pressed and this menu is attached to
  673.      an icon, we should deal with this event, otherwise pass back to Event
  674.      module.
  675.   */
  676.   if ((mouse->button.data.menu) ||
  677.       ((handler->flags.data.icon_handler) && (mouse->button.data.select)))
  678.   {
  679.     menu m;
  680.  
  681.     /* Menu requested  - set up window/icon variables */
  682.     menu_current_window = mouse->window;
  683.     menu_current_icon   = mouse->icon;
  684.                                       
  685.     /* Call menu maker if necessary */
  686.     if (handler->flags.data.menu_maker)
  687.       m = (handler->data.maker)(handler->reference);
  688.     else
  689.       m = handler->data.menu;
  690.  
  691.     /* De-install any handlers still installed... */
  692.     if (installed_handler != NULL)
  693.     {
  694.       Event_Release(event_MENU, event_ANY, event_ANY, 
  695.                     Menu__SelectionHandler, (void *) installed_handler);
  696.       EventMsg_Release(message_MENUWARNING, event_ANY, Menu__MessageHandler);
  697.     }
  698.  
  699.     /* Request menu selections and warning messages to be delivered to us... */
  700.     Event_Claim(event_MENU, event_ANY, event_ANY, 
  701.                 Menu__SelectionHandler, (void *) handler);
  702.     EventMsg_Claim(message_MENUWARNING, event_ANY, 
  703.                    Menu__MessageHandler, (void *) handler);
  704.   
  705.     /* Update the variable which records the currently installed handler */
  706.     installed_handler = handler;
  707.  
  708.     /* And show the menu */
  709.     Menu_Show(m, mouse->pos, handler->flags.data.iconbar);
  710.  
  711.     return TRUE;
  712.   }
  713.  
  714.   return FALSE;
  715.  
  716. }
  717.  
  718. static window_node *find_window_node(window_handle window)
  719. {
  720.   /* Looks through the handlers to see if one is registered for this 
  721.      window handle already.  Returns pointer to it if it is, NULL otherwise.
  722.   */
  723.   window_node *windownode = LinkList_FirstItem(&handlers);
  724.  
  725.   /* Search for specified window handle */
  726.   while ((windownode != NULL) && (windownode->window != window))
  727.     windownode = (window_node *) LinkList_NextItem(&windownode->header);
  728.  
  729.   /* Return what we found (possibly NULL => no window node found */
  730.   return windownode;
  731. }
  732.  
  733. static icon_node *find_icon_node(linklist_header *anchor, icon_handle icon)
  734. {
  735.   /* Looks through the given icon handlers to see if one is registered for this 
  736.      icon handle already.  Returns pointer to it if it is, NULL otherwise.
  737.   */
  738.   icon_node *iconnode = LinkList_FirstItem(anchor);
  739.  
  740.   /* Search for specified icon handle */
  741.   while ((iconnode != NULL) && (iconnode->icon != icon))
  742.     iconnode = (icon_node *) LinkList_NextItem(&iconnode->header);
  743.  
  744.   /* Return what we found (possibly NULL => no icon node found */
  745.   return iconnode;
  746. }
  747.  
  748. static handler_str *find_menu_handler(window_handle window, icon_handle icon)
  749. {
  750.   window_node *windownode;
  751.   icon_node   *iconnode;
  752.  
  753.   /* This returns a pointer to a handler_str for the caller to fill in.
  754.      It searches for an existing handler to overwrite, or failing that, creates
  755.      a new node in the tree for the specified window/icon handle pair, and returns
  756.      a pointer to the handler_str of that.
  757.      i.e. it's a 'constructive' find - if it can't find the entity it's looking for,
  758.      it makes it.
  759.   */
  760.  
  761.   /* Is there already a handler for this window? */
  762.  
  763.   windownode = find_window_node(window);
  764.  
  765.   if (windownode == NULL)
  766.   {
  767.     /* There isn't - so add one */
  768.  
  769.     windownode = (window_node *) malloc(sizeof(window_node));
  770.     windownode->window = window;
  771.     LinkList_InitItem(&windownode->icons_anchor); /* No icon specifics (yet) */
  772.  
  773.     /* If icon-specific, we should null the window-specific handler... */
  774.     if (icon != event_ANY)
  775.     {
  776.       /* The handler_proc is the flag - if it's NULL, there's no menu or
  777.          menu maker installed either.
  778.       */
  779.       windownode->handler.handler_proc = NULL;
  780.     }
  781.  
  782.     /* Add to head of list - except event_ANY, which should always be last */
  783.  
  784.     if (window == event_ANY)
  785.       LinkList_AddToTail(&handlers, &(windownode->header));
  786.     else
  787.       LinkList_AddToHead(&handlers, &(windownode->header));
  788.   }
  789.   else if (icon == event_ANY)
  790.   {
  791.     /* If there is a window-specific handler already, remove it */
  792.     if (windownode->handler.handler_proc != NULL)
  793.       Event_Release(event_CLICK, window, event_ANY, Menu__EventHandler, 
  794.                     (void *) &windownode->handler);
  795.   }
  796.  
  797.   /* If not interested in icon-specific handler, just return pointer to
  798.      window-specific handler */
  799.   if ((window == event_ANY) || (icon == event_ANY))
  800.     return &windownode->handler;
  801.  
  802.   /* Is there already a handler for this icon? */
  803.   iconnode = find_icon_node(&windownode->icons_anchor, icon);
  804.  
  805.   if (iconnode == NULL)
  806.   {
  807.     /* No icon handler for this icon, so add one */
  808.  
  809.     iconnode = (icon_node *) malloc(sizeof(icon_node));
  810.     iconnode->icon = icon;
  811.  
  812.     /* Add to the icon handler list */
  813.     LinkList_AddToTail(&windownode->icons_anchor, &(iconnode->header));
  814.   }
  815.   else
  816.   {
  817.     /* Remove the handler which is already present */
  818.     Event_Release(event_CLICK, window, icon, Menu__EventHandler, 
  819.                   (void *) &iconnode->handler);
  820.   }
  821.  
  822.   return &iconnode->handler;
  823. }
  824.  
  825.  
  826. /* This is used to set the flag in the handler_str. */
  827. static BOOL Menu__menu_maker = FALSE;
  828.  
  829. void Menu_AttachMenu(menu the_menu, window_handle window, icon_handle icon,
  830.                       menu_handler handler_proc, void *reference)
  831. {
  832.   handler_str *handler = find_menu_handler(window, icon);
  833.  
  834.   handler->flags.data.menu_maker   = Menu__menu_maker;
  835.   handler->flags.data.icon_handler = (icon != event_ANY);
  836.   handler->flags.data.iconbar      = (window == window_ICONBAR);
  837.   handler->data.menu               = the_menu;
  838.   handler->handler_proc            = handler_proc;
  839.   handler->reference               = reference;
  840.  
  841.   /* Claim mouse click events for given window/icon handle pair */
  842.   Event_Claim(event_CLICK, window, icon, Menu__EventHandler, (void *) handler);
  843. }
  844.  
  845.  
  846. void Menu_AttachMenuMaker(menu_maker maker_proc, 
  847.                            window_handle window, icon_handle icon,
  848.                            menu_handler handler_proc, void *reference)
  849. {
  850.   /* Bit of a cheat this - relies on union structure of handler_str, but why
  851.      duplicate code?
  852.   */
  853.   Menu__menu_maker = TRUE;
  854.   Menu_AttachMenu((menu) maker_proc, window, icon, handler_proc, reference);
  855.   Menu__menu_maker = FALSE;
  856. }
  857.  
  858. void Menu_DetachMenu(window_handle window, icon_handle icon)
  859. /*
  860.    Detaches any menus or menu makers from the given window/icon.
  861.    If called for a window, detaches *all* handlers for it, including
  862.    icon specific ones.
  863.    If the window is event_ANY, it ONLY removes the 'catch-all' handler - it
  864.    does NOT remove all menu handlers.
  865.  
  866.    It is legal to call this even if no menus/makers are attached to the
  867.    specified window/icon.
  868.  
  869.    window    should be event_ANY  - if not window specific, or
  870.              a window handle      - to detach only from that window
  871.    icon      should be event_ANY  - if not icon-specific, or
  872.              an icon handle       - to detach ONLY from that icon
  873.              (NOTE: if icon != event_ANY, window MUST be defined)
  874. */
  875. {
  876.   window_node *windownode = find_window_node(window);
  877.   icon_node   *iconnode;
  878.  
  879.   if (windownode == NULL)
  880.     /* Couldn't find any handlers for specified window */
  881.     return;
  882.  
  883.   if (icon == event_ANY)
  884.   {
  885.     icon_node *tmp;
  886.  
  887.     /* Release the events claimed by this window-specific handler */
  888.     Event_Release(event_CLICK, window, event_ANY, Menu__EventHandler, 
  889.                   (void *) &windownode->handler);
  890.  
  891.     /* Remove this window's node */
  892.     LinkList_Unlink(&handlers, &(windownode->header));
  893.  
  894.     /* Free the icon handler nodes for this window (if any exist) */
  895.  
  896.     iconnode = LinkList_FirstItem(&windownode->icons_anchor);
  897.  
  898.     /* Don't need to unlink each node as we're deleting the whole list
  899.        including the anchor anyway.
  900.     */
  901.     while (iconnode != NULL)
  902.     {
  903.       tmp = iconnode;
  904.       iconnode = LinkList_NextItem(&iconnode->header);
  905.       free(tmp);
  906.     }
  907.  
  908.     /* Free the window node itself */
  909.     free(windownode);
  910.   } 
  911.   else
  912.   {
  913.     /* Remove handler for this icon */
  914.     iconnode = find_icon_node(&windownode->icons_anchor, icon);
  915.     
  916.     /* If no handler for this window/icon handle pair, exit quietly */
  917.     if (iconnode == NULL)
  918.       return;
  919.  
  920.     /* Release the events claimed by this icon-specific handler */
  921.     Event_Release(event_CLICK, window, icon, Menu__EventHandler, 
  922.                   (void *) &iconnode->handler);
  923.  
  924.     /* Remove the icon node from the window's list of icon handlers */
  925.     LinkList_Unlink(&(windownode->icons_anchor), &(iconnode->header));
  926.  
  927.     /* Free the memory used for this node */
  928.     free(iconnode);
  929.  
  930.     /* If this was the last icon handler for this window, and there is no
  931.        window-specific handler, delete the window node as well.
  932.     */
  933.     if ((LinkList_FirstItem(&windownode->icons_anchor) == NULL) &&
  934.         (windownode->handler.handler_proc == NULL))
  935.     {
  936.       LinkList_Unlink(&handlers, &(windownode->header));
  937.       free(windownode);
  938.     }
  939.   }
  940. }
  941.  
  942.