Added mouse-highlight variable to turn off mouse highlight or
[bpt/emacs.git] / src / w32menu.c
index 638d85b..ba15125 100644 (file)
@@ -61,6 +61,8 @@ enum button_type
   BUTTON_TYPE_RADIO
 };
 
+/* This structure is based on the one in ../lwlib/lwlib.h, modified
+   for Windows.  */
 typedef struct _widget_value
 {
   /* name of widget */
@@ -69,8 +71,10 @@ typedef struct _widget_value
   char*                value;
   /* keyboard equivalent. no implications for XtTranslations */ 
   char*                key;
-  /* Help string or null if none.  */
-  char         *help;
+  /* Help string or nil if none.
+     GC finds this string through the frame's menu_bar_vector
+     or through menu_items.  */
+  Lisp_Object  help;
   /* true if enabled */
   Boolean      enabled;
   /* true if selected */
@@ -119,6 +123,11 @@ typedef struct _widget_value
 #define FALSE 0
 #endif /* no TRUE */
 
+static HMENU current_popup_menu;
+
+FARPROC get_menu_item_info;
+FARPROC set_menu_item_info;
+
 Lisp_Object Vmenu_updating_frame;
 
 Lisp_Object Qdebug_on_next_call;
@@ -1134,6 +1143,7 @@ single_submenu (item_key, item_name, maps)
   wv->value = 0;
   wv->enabled = 1;
   wv->button_type = BUTTON_TYPE_NONE;
+  wv->help = Qnil;
   first_wv = wv;
   save_wv = 0;
   prev_wv = 0;
@@ -1206,6 +1216,7 @@ single_submenu (item_key, item_name, maps)
              wv->value = 0;
              wv->enabled = 1;
              wv->button_type = BUTTON_TYPE_NONE;
+             wv->help = Qnil;
            }
          save_wv = wv;
          prev_wv = 0;
@@ -1264,10 +1275,10 @@ single_submenu (item_key, item_name, maps)
            abort ();
 
          wv->selected = !NILP (selected);
-         if (STRINGP (help))
-           wv->help = (char *) XSTRING (help)->data;
-          else
-            wv->help = NULL;
+         if (!STRINGP (help))
+           help = Qnil;
+
+         wv->help = help;
 
          prev_wv = wv;
 
@@ -1318,6 +1329,7 @@ set_frame_menubar (f, first_time, deep_p)
   wv->value = 0;
   wv->enabled = 1;
   wv->button_type = BUTTON_TYPE_NONE;
+  wv->help = Qnil;
   first_wv = wv;
 
   if (deep_p)
@@ -1416,7 +1428,10 @@ set_frame_menubar (f, first_time, deep_p)
        }
 
       /* Now GC cannot happen during the lifetime of the widget_value,
-        so it's safe to store data from a Lisp_String.  */
+        so it's safe to store data from a Lisp_String, as long as
+        local copies are made when the actual menu is created.
+        Windows takes care of this for normal string items, but
+        not for owner-drawn items or additional item-info.  */
       wv = first_wv->contents;
       for (i = 0; i < XVECTOR (items)->size; i += 4)
        {
@@ -1451,6 +1466,7 @@ set_frame_menubar (f, first_time, deep_p)
          wv->value = 0;
          wv->enabled = 1;
          wv->button_type = BUTTON_TYPE_NONE;
+         wv->help = Qnil;
          /* This prevents lwlib from assuming this
             menu item is really supposed to be empty.  */
          /* The EMACS_INT cast avoids a warning.
@@ -1592,6 +1608,7 @@ w32_menu_show (f, x, y, for_click, keymaps, title, error)
   wv->value = 0;
   wv->enabled = 1;
   wv->button_type = BUTTON_TYPE_NONE;
+  wv->help = Qnil;
   first_wv = wv;
   first_pane = 1;
  
@@ -1658,6 +1675,7 @@ w32_menu_show (f, x, y, for_click, keymaps, title, error)
              wv->value = 0;
              wv->enabled = 1;
              wv->button_type = BUTTON_TYPE_NONE;
+             wv->help = Qnil;
              save_wv = wv;
              prev_wv = 0;
            }
@@ -1719,8 +1737,10 @@ w32_menu_show (f, x, y, for_click, keymaps, title, error)
            abort ();
 
          wv->selected = !NILP (selected);
-          if (STRINGP (help))
-            wv->help = XSTRING (help)->data;
+          if (!STRINGP (help))
+           help = Qnil;
+
+         wv->help = help;
 
          prev_wv = wv;
 
@@ -1738,6 +1758,7 @@ w32_menu_show (f, x, y, for_click, keymaps, title, error)
         so that it looks better.  Having two separators looks odd.  */
       wv_sep->name = "--";
       wv_sep->next = first_wv->contents;
+      wv_sep->help = Qnil;
 
 #ifndef HAVE_MULTILINGUAL_MENU
       if (STRING_MULTIBYTE (title))
@@ -1747,12 +1768,13 @@ w32_menu_show (f, x, y, for_click, keymaps, title, error)
       wv_title->enabled = TRUE;
       wv_title->title = TRUE;
       wv_title->button_type = BUTTON_TYPE_NONE;
+      wv_title->help = Qnil;
       wv_title->next = wv_sep;
       first_wv->contents = wv_title;
     }
 
   /* Actually create the menu.  */
-  menu = CreatePopupMenu ();
+  current_popup_menu = menu = CreatePopupMenu ();
   fill_in_menu (menu, first_wv->contents);
 
   /* Adjust coordinates to be root-window-relative.  */
@@ -1836,6 +1858,7 @@ w32_menu_show (f, x, y, for_click, keymaps, title, error)
 }
 \f
 
+#ifdef HAVE_DIALOGS
 static char * button_names [] = {
   "button1", "button2", "button3", "button4", "button5",
   "button6", "button7", "button8", "button9", "button10" };
@@ -1881,6 +1904,7 @@ w32_dialog_show (f, keymaps, title, error)
       prev_wv->name++;
     prev_wv->enabled = 1;
     prev_wv->name = "message";
+    prev_wv->help = Qnil;
     first_wv = prev_wv;
  
     /* Loop over all panes and items, filling in the tree.  */
@@ -1926,6 +1950,7 @@ w32_dialog_show (f, keymaps, title, error)
        wv->value = (char *) XSTRING (item_name)->data;
        wv->call_data = (void *) &XVECTOR (menu_items)->contents[i];
        wv->enabled = !NILP (enable);
+       wv->help = Qnil;
        prev_wv = wv;
 
        if (! boundary_seen)
@@ -1942,6 +1967,7 @@ w32_dialog_show (f, keymaps, title, error)
 
     wv = xmalloc_widget_value ();
     wv->name = dialog_name;
+    wv->help = Qnil;
 
     /* Dialog boxes use a really stupid name encoding
        which specifies how many buttons to use
@@ -1959,13 +1985,11 @@ w32_dialog_show (f, keymaps, title, error)
   }
 
   /* Actually create the dialog.  */
-#ifdef HAVE_DIALOGS
   dialog_id = widget_id_tick++;
   menu = lw_create_widget (first_wv->name, "dialog", dialog_id, first_wv,
                           f->output_data.w32->widget, 1, 0,
                           dialog_selection_callback, 0);
   lw_modify_all_widgets (dialog_id, first_wv->contents, TRUE);
-#endif
 
   /* Free the widget_value objects we used to specify the contents.  */
   free_menubar_widget_value_tree (first_wv);
@@ -1974,7 +1998,6 @@ w32_dialog_show (f, keymaps, title, error)
   menu_item_selection = 0;
 
   /* Display the menu.  */
-#ifdef HAVE_DIALOGS
   lw_pop_up_all_widgets (dialog_id);
   popup_activated_flag = 1;
 
@@ -1982,7 +2005,6 @@ w32_dialog_show (f, keymaps, title, error)
   popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f), dialog_id);
 
   lw_destroy_all_widgets (dialog_id); 
-#endif
 
   /* Find the selected item, and its pane, to return
      the proper value.  */
@@ -2023,6 +2045,7 @@ w32_dialog_show (f, keymaps, title, error)
 
   return Qnil;
 }
+#endif  /* HAVE_DIALOGS  */
 \f
 
 /* Is this item a separator? */
@@ -2077,16 +2100,24 @@ add_menu_item (HMENU menu, widget_value *wv, HMENU item)
       else
        out_string = wv->name;
 
-      if (wv->title)
+      if (item != NULL)
+       fuFlags = MF_POPUP;
+      else if (wv->title || wv->call_data == 0)
        {
-#if 0  /* no GC while popup menu is active */
-         out_string = LocalAlloc (0, strlen (wv->name) + 1);
-         strcpy (out_string, wv->name);
+         /* Only use MF_OWNERDRAW if GetMenuItemInfo is usable, since
+            we can't deallocate the memory otherwise.  */
+         if (get_menu_item_info)
+           {
+             out_string = (char *) LocalAlloc (LPTR, strlen (wv->name) + 1);
+#ifdef MENU_DEBUG
+             DebPrint ("Menu: allocing %ld for owner-draw", info.dwItemData);
 #endif
-         fuFlags = MF_OWNERDRAW | MF_DISABLED;
+             strcpy (out_string, wv->name);
+             fuFlags = MF_OWNERDRAW | MF_DISABLED;
+           }
+         else
+           fuFlags = MF_DISABLED;
        }
-      else if (wv->call_data == 0)
-       fuFlags |= MF_DISABLED;
 
       /* Draw radio buttons and tickboxes. */
       else if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
@@ -2096,9 +2127,6 @@ add_menu_item (HMENU menu, widget_value *wv, HMENU item)
        fuFlags |= MF_UNCHECKED;
     }
 
-  if (item != NULL)
-    fuFlags = MF_POPUP;
-
   return_value =
     AppendMenu (menu,
                 fuFlags,
@@ -2108,9 +2136,6 @@ add_menu_item (HMENU menu, widget_value *wv, HMENU item)
   /* This must be done after the menu item is created.  */
   if (!wv->title && wv->call_data != 0)
     {
-      HMODULE user32 = GetModuleHandle ("user32.dll");
-      FARPROC set_menu_item_info = GetProcAddress (user32, "SetMenuItemInfoA");
-
       if (set_menu_item_info)
        {
          MENUITEMINFO info;
@@ -2118,8 +2143,11 @@ add_menu_item (HMENU menu, widget_value *wv, HMENU item)
          info.cbSize = sizeof (info);
          info.fMask = MIIM_DATA;
 
-         /* Set help string for menu item.  */
-         info.dwItemData = (DWORD)wv->help;
+         /* Set help string for menu item.  Leave it as a Lisp_Object
+            until it is ready to be displayed, since GC can happen while
+            menus are active.  */
+         if (wv->help)
+           info.dwItemData = (DWORD) wv->help;
 
          if (wv->button_type == BUTTON_TYPE_RADIO)
            {
@@ -2183,21 +2211,25 @@ popup_activated ()
 void
 w32_menu_display_help (HWND owner, HMENU menu, UINT item, UINT flags)
 {
-  HMODULE user32 = GetModuleHandle ("user32.dll");
-  FARPROC get_menu_item_info = GetProcAddress (user32, "GetMenuItemInfoA");
-
   if (get_menu_item_info)
     {
-      MENUITEMINFO info;
       struct frame *f = x_window_to_frame (&one_w32_display_info, owner);
       Lisp_Object frame, help;
 
-      bzero (&info, sizeof (info));
-      info.cbSize = sizeof (info);
-      info.fMask = MIIM_DATA;
-      get_menu_item_info (menu, item, FALSE, &info);
+      // No help echo on owner-draw menu items.
+      if (flags & MF_OWNERDRAW || flags & MF_POPUP)
+       help = Qnil;
+      else
+       {
+         MENUITEMINFO info;
 
-      help = info.dwItemData ? build_string ((char *)info.dwItemData) : Qnil;
+         bzero (&info, sizeof (info));
+         info.cbSize = sizeof (info);
+         info.fMask = MIIM_DATA;
+         get_menu_item_info (menu, item, FALSE, &info);
+
+         help = info.dwItemData ? (Lisp_Object) info.dwItemData : Qnil;
+       }
 
       /* Store the help echo in the keyboard buffer as the X toolkit
         version does, rather than directly showing it. This seems to
@@ -2215,15 +2247,71 @@ w32_menu_display_help (HWND owner, HMENU menu, UINT item, UINT flags)
     }
 }
 
+/* Free memory used by owner-drawn strings.  */
+static void
+w32_free_submenu_strings (menu)
+     HMENU menu;
+{
+  int i, num = GetMenuItemCount (menu);
+  for (i = 0; i < num; i++)
+    {
+      MENUITEMINFO info;
+      bzero (&info, sizeof (info));
+      info.cbSize = sizeof (info);
+      info.fMask = MIIM_DATA | MIIM_TYPE | MIIM_SUBMENU;
+
+      get_menu_item_info (menu, i, TRUE, &info);
 
+      /* Owner-drawn names are held in dwItemData.  */
+      if ((info.fType & MF_OWNERDRAW) && info.dwItemData)
+       {
+#ifdef MENU_DEBUG
+         DebPrint ("Menu: freeing %ld for owner-draw", info.dwItemData);
+#endif
+         LocalFree (info.dwItemData);
+       }
+
+      /* Recurse down submenus.  */
+      if (info.hSubMenu)
+       w32_free_submenu_strings (info.hSubMenu);
+    }
+}
+
+void
+w32_free_menu_strings (hwnd)
+     HWND hwnd;
+{
+  HMENU menu = current_popup_menu;
+
+  if (get_menu_item_info)
+    {
+      /* If there is no popup menu active, free the strings from the frame's
+        menubar.  */
+      if (!menu)
+       menu = GetMenu (hwnd);
+
+      if (menu)
+       w32_free_submenu_strings (menu);
+    }
+
+  current_popup_menu = NULL;
+}
 
 #endif /* HAVE_MENUS */
+
 \f
 syms_of_w32menu ()
 {
+  /* See if Get/SetMenuItemInfo functions are available.  */
+  HMODULE user32 = GetModuleHandle ("user32.dll");
+  get_menu_item_info = GetProcAddress (user32, "GetMenuItemInfoA");
+  set_menu_item_info = GetProcAddress (user32, "SetMenuItemInfoA");
+
   staticpro (&menu_items);
   menu_items = Qnil;
 
+  current_popup_menu = NULL;
+
   Qdebug_on_next_call = intern ("debug-on-next-call");
   staticpro (&Qdebug_on_next_call);