33f456a7b61f3aae5844f2c5bd56e2037850ed12
[chise/xemacs-chise.git.1] / src / menubar-msw.c
1 /* Implements an elisp-programmable menubar -- Win32
2    Copyright (C) 1993, 1994 Free Software Foundation, Inc.
3    Copyright (C) 1995 Tinker Systems and INS Engineering Corp.
4    Copyright (C) 1997 Kirill M. Katsnelson <kkm@kis.ru>.
5    Copyright (C) 2000 Ben Wing.
6
7 This file is part of XEmacs.
8
9 XEmacs is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License as published by the
11 Free Software Foundation; either version 2, or (at your option) any
12 later version.
13
14 XEmacs is distributed in the hope that it will be useful, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
17 for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with XEmacs; see the file COPYING.  If not, write to
21 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 Boston, MA 02111-1307, USA.  */
23
24 /* Synched up with: Not in FSF. */
25
26 /* Author:
27    Initially written by kkm 12/24/97,
28    peeking into and copying stuff from menubar-x.c
29    */
30
31 /* Algorithm for handling menus is as follows. When window's menubar
32  * is created, current-menubar is not traversed in depth. Rather, only
33  * top level items, both items and pulldowns, are added to the
34  * menubar. Each pulldown is initially empty. When a pulldown is
35  * selected and about to open, corresponding element of
36  * current-menubar is found, and the newly open pulldown is
37  * populated. This is made again in the same non-recursive manner.
38  *
39  * This algorithm uses hash tables to find out element of the menu
40  * descriptor list given menu handle. The key is an opaque ptr data
41  * type, keeping menu handle, and the value is a list of strings
42  * representing the path from the root of the menu to the item
43  * descriptor. Each frame has an associated hash table.
44  *
45  * Leaf items are assigned a unique id based on item's hash. When an
46  * item is selected, Windows sends back the id. Unfortunately, only
47  * low 16 bit of the ID are sent, and there's no way to get the 32-bit
48  * value. Yes, Win32 is just a different set of bugs than X! Aside
49  * from this blame, another hashing mechanism is required to map menu
50  * ids to commands (which are actually Lisp_Object's). This mapping is
51  * performed in the same hash table, as the lifetime of both maps is
52  * exactly the same. This is unambigous, as menu handles are
53  * represented by lisp opaques, while command ids are by lisp
54  * integers. The additional advantage for this is that command forms
55  * are automatically GC-protected, which is important because these
56  * may be transient forms generated by :filter functions.
57  *
58  * The hash table is not allowed to grow too much; it is pruned
59  * whenever this is safe to do. This is done by re-creating the menu
60  * bar, and clearing and refilling the hash table from scratch.
61  *
62  * Popup menus are handled identically to pulldowns. A static hash
63  * table is used for popup menus, and lookup is made not in
64  * current-menubar but in a lisp form supplied to the `popup'
65  * function.
66  *
67  * Another Windows weirdness is that there's no way to tell that a
68  * popup has been dismissed without making selection. We need to know
69  * that to cleanup the popup menu hash table, but this is not honestly
70  * doable using *documented* sequence of messages. Sticking to
71  * particular knowledge is bad because this may break in Windows NT
72  * 5.0, or Windows 98, or other future version. Instead, I allow the
73  * hash tables to hang around, and not clear them, unless WM_COMMAND is
74  * received. This is worth some memory but more safe. Hacks welcome,
75  * anyways!
76  *
77  */
78
79 #include <config.h>
80 #include "lisp.h"
81
82 #include "buffer.h"
83 #include "commands.h"
84 #include "console-msw.h"
85 #include "elhash.h"
86 #include "events.h"
87 #include "frame.h"
88 #include "gui.h"
89 #include "lisp.h"
90 #include "menubar.h"
91 #include "menubar-msw.h"
92 #include "opaque.h"
93 #include "window.h"
94
95 /* #### */
96 #define REPLACE_ME_WITH_GLOBAL_VARIABLE_WHICH_CONTROLS_RIGHT_FLUSH 0
97
98 #define EMPTY_ITEM_ID ((UINT)LISP_TO_VOID (Qunbound))
99 #define EMPTY_ITEM_NAME "(empty)"
100
101 /* Current menu (bar or popup) descriptor. gcpro'ed */
102 static Lisp_Object current_menudesc;
103
104 /* Current menubar or popup hash table. gcpro'ed */
105 static Lisp_Object current_hash_table;
106
107 /* This is used to allocate unique ids to menu items.
108    Items ids are in MENU_ITEM_ID_MIN to MENU_ITEM_ID_MAX.
109    Allocation checks that the item is not already in
110    the TOP_LEVEL_MENU */
111
112 /* #### defines go to gui-msw.h, as the range is shared with toolbars
113    (If only toolbars will be implemented as common controls) */
114 #define MENU_ITEM_ID_MIN 0x8000
115 #define MENU_ITEM_ID_MAX 0xFFFF
116 #define MENU_ITEM_ID_BITS(x) (((x) & 0x7FFF) | 0x8000)
117 static HMENU top_level_menu;
118
119 /* Translate (in place) %_ to &, %% to %.
120    Return new length, and (through accel) the accelerator character.
121    (If there is no accelerator, it will be added on the first character.)
122    len = number of bytes (not including zero terminator).
123    maxlen = size of buffer.
124    We assume and maintain zero-termination.  To be absolutely sure
125    of not hitting an error, maxlen should be >= 2*len + 3. */
126
127 Bytecount
128 mswindows_translate_menu_or_dialog_item (Bufbyte *item, Bytecount len,
129                                    Bytecount maxlen, Emchar *accel,
130                                    Lisp_Object error_name)
131 {
132   Bufbyte *ptr;
133
134   *accel = '\0';
135
136   /* Escape '&' as '&&' */
137   
138   ptr = item;
139   while ((ptr = (Bufbyte *) memchr (ptr, '&', len - (ptr - item))) != NULL)
140     {
141       if (len + 2 > maxlen)
142         signal_simple_error ("Menu item produces too long displayable string",
143                              error_name);
144       memmove (ptr + 1, ptr, (len - (ptr - item)) + 1);
145       len++;
146       ptr += 2;
147     }
148
149   /* Replace XEmacs accelerator '%_' with Windows accelerator '&'
150      and `%%' with `%'. */
151   ptr = item;
152   while ((ptr = memchr (ptr, '%', len - (ptr - item))) != NULL)
153     {
154       if (*(ptr + 1) == '_')
155         {
156           *ptr = '&';
157           if (!*accel)
158             /* #### urk !  We need a reference translation table for
159                case changes that aren't buffer-specific. */
160             *accel = DOWNCASE (current_buffer, charptr_emchar (ptr + 2));
161           memmove (ptr + 1, ptr + 2, len - (ptr - item + 2) + 1);
162           len--;
163         }
164       else if (*(ptr + 1) == '%')
165         {
166           memmove (ptr + 1, ptr + 2, len - (ptr - item + 2) + 1);
167           len--;
168         }
169       ptr++;
170     }
171
172   if (!*accel)
173     {
174       if (len + 2 > maxlen)
175         signal_simple_error ("Menu item produces too long displayable string",
176                              error_name);
177       ptr = item;
178       memmove (ptr + 1, ptr, len + 1);
179       /* #### urk !  We need a reference translation table for
180          case changes that aren't buffer-specific. */
181       *accel = DOWNCASE (current_buffer, charptr_emchar (ptr + 1));
182       *ptr = '&';
183
184       len++;
185     }
186
187   return len;
188 }
189
190 /*
191  * This returns Windows-style menu item string:
192  * "Left Flush\tRight Flush"
193  */
194
195 /* #### This is junk.  Need correct handling of sizes.  Use a Bufbyte_dynarr,
196    not a static buffer. */
197 static char*
198 displayable_menu_item (Lisp_Object gui_item, int bar_p, Emchar *accel)
199 {
200   unsigned int ll;
201
202   /* We construct the name in a static buffer. That's fine, because
203      menu items longer than 128 chars are probably programming errors,
204      and better be caught than displayed! */
205   
206   static char buf[MAX_MENUITEM_LENGTH+2];
207
208   /* Left flush part of the string */
209   ll = gui_item_display_flush_left (gui_item, buf, MAX_MENUITEM_LENGTH);
210
211   ll = mswindows_translate_menu_or_dialog_item ((Bufbyte *) buf, ll,
212                                           MAX_MENUITEM_LENGTH, accel,
213                                           XGUI_ITEM (gui_item)->name);
214
215   /* Right flush part, unless we're at the top-level where it's not allowed */
216   if (!bar_p)
217     {
218       unsigned int lr;
219
220       assert (MAX_MENUITEM_LENGTH > ll + 1);
221       lr = gui_item_display_flush_right (gui_item, buf + ll + 1,
222                                          MAX_MENUITEM_LENGTH - ll - 1);
223       if (lr)
224         buf [ll] = '\t';
225      }
226
227   return buf;
228 }
229
230 /*
231  * hmenu_to_lisp_object() returns an opaque ptr given menu handle.
232  */
233 static Lisp_Object
234 hmenu_to_lisp_object (HMENU hmenu)
235 {
236   return make_opaque_ptr (hmenu);
237 }
238
239 /*
240  * Allocation tries a hash based on item's path and name first. This
241  * almost guarantees that the same item will override its old value in
242  * the hash table rather than abandon it.
243  */
244 static Lisp_Object
245 allocate_menu_item_id (Lisp_Object path, Lisp_Object name, Lisp_Object suffix)
246 {
247   UINT id = MENU_ITEM_ID_BITS (HASH3 (internal_hash (path, 0),
248                                       internal_hash (name, 0),
249                                       internal_hash (suffix, 0)));
250   do {
251       id = MENU_ITEM_ID_BITS (id + 1);
252   } while (GetMenuState (top_level_menu, id, MF_BYCOMMAND) != 0xFFFFFFFF);
253   return make_int (id);
254 }
255
256 static HMENU
257 create_empty_popup_menu (void)
258 {
259   return CreatePopupMenu ();
260 }
261
262 static void
263 empty_menu (HMENU menu, int add_empty_p)
264 {
265   while (DeleteMenu (menu, 0, MF_BYPOSITION));
266   if (add_empty_p)
267     AppendMenu (menu, MF_STRING | MF_GRAYED, EMPTY_ITEM_ID, EMPTY_ITEM_NAME);
268 }
269
270 /*
271  * The idea of checksumming is that we must hash minimal object
272  * which is necessarily changes when the item changes. For separator
273  * this is a constant, for grey strings and submenus these are hashes
274  * of names, since submenus are unpopulated until opened so always
275  * equal otherwise. For items, this is a full hash value of a callback,
276  * because a callback may me a form which can be changed only somewhere
277  * in depth.
278  */
279 static unsigned long
280 checksum_menu_item (Lisp_Object item)
281 {
282   if (STRINGP (item))
283     {
284       /* Separator or unselectable text - hash as a string + 13 */
285       if (separator_string_p (XSTRING_DATA (item)))
286         return 13;
287       else
288         return internal_hash (item, 0) + 13;
289     }
290   else if (CONSP (item))
291     {
292       /* Submenu - hash by its string name + 0 */
293       return internal_hash (XCAR(item), 0);
294     }
295   else if (VECTORP (item))
296     {
297       /* An ordinary item - hash its name and callback form. */
298       return HASH2 (internal_hash (XVECTOR_DATA(item)[0], 0),
299                     internal_hash (XVECTOR_DATA(item)[1], 0));
300     }
301  
302   /* An error - will be caught later */
303   return 0;
304 }
305
306 static void
307 populate_menu_add_item (HMENU menu, Lisp_Object path,
308                         Lisp_Object hash_tab, Lisp_Object item,
309                         Lisp_Object *accel_list,
310                         int flush_right, int bar_p)
311 {
312   MENUITEMINFO item_info;
313
314   item_info.cbSize = sizeof (item_info);
315   item_info.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
316   item_info.fState = 0;
317   item_info.wID = 0;
318   item_info.fType = 0;
319
320   if (STRINGP (item))
321     {
322       /* Separator or unselectable text */
323       if (separator_string_p (XSTRING_DATA (item)))
324         item_info.fType = MFT_SEPARATOR;
325       else
326         {
327           item_info.fType = MFT_STRING;
328           item_info.fState = MFS_DISABLED;
329           item_info.dwTypeData = XSTRING_DATA (item);
330         }
331     }
332   else if (CONSP (item))
333     {
334       /* Submenu */
335       HMENU submenu;
336       Lisp_Object gui_item = allocate_gui_item ();
337       Lisp_Gui_Item *pgui_item = XGUI_ITEM (gui_item);
338       struct gcpro gcpro1, gcpro2, gcpro3;
339       Emchar accel;
340
341       GCPRO3 (gui_item, path, *accel_list);
342
343       menu_parse_submenu_keywords (item, gui_item);
344
345       if (!STRINGP (pgui_item->name))
346         signal_simple_error ("Menu name (first element) must be a string",
347                              item);
348
349       if (!gui_item_included_p (gui_item, Vmenubar_configuration))
350       {
351         UNGCPRO;
352         goto done;
353       }
354
355       if (!gui_item_active_p (gui_item))
356         item_info.fState = MFS_GRAYED;
357       /* Temptation is to put 'else' right here. Although, the
358          displayed item won't have an arrow indicating that it is a
359          popup.  So we go ahead a little bit more and create a popup */
360       submenu = create_empty_popup_menu ();
361
362       item_info.fMask |= MIIM_SUBMENU;
363       item_info.dwTypeData = displayable_menu_item (gui_item, bar_p, &accel);
364       item_info.hSubMenu = submenu;
365
366       if (accel && bar_p)
367         *accel_list = Fcons (make_char (accel), *accel_list);
368
369       if (!(item_info.fState & MFS_GRAYED))
370         {
371           /* Now add the full submenu path as a value to the hash table,
372              keyed by menu handle */
373           if (NILP(path))
374             path = list1 (pgui_item->name);
375           else
376             {
377               Lisp_Object arg[2];
378               arg[0] = path;
379               arg[1] = list1 (pgui_item->name);
380               path = Fappend (2, arg);
381             }
382
383           Fputhash (hmenu_to_lisp_object (submenu), path, hash_tab);
384         }
385       UNGCPRO;
386     } 
387   else if (VECTORP (item))
388     {
389       /* An ordinary item */
390       Lisp_Object style, id;
391       Lisp_Object gui_item = gui_parse_item_keywords (item);
392       Lisp_Gui_Item *pgui_item = XGUI_ITEM (gui_item);
393       struct gcpro gcpro1, gcpro2;
394       Emchar accel;
395
396       GCPRO2 (gui_item, *accel_list);
397
398       if (!gui_item_included_p (gui_item, Vmenubar_configuration))
399       {
400         UNGCPRO;
401         goto done;
402       }
403
404       if (!STRINGP (pgui_item->name))
405         pgui_item->name = Feval (pgui_item->name);
406
407       if (!gui_item_active_p (gui_item))
408         item_info.fState = MFS_GRAYED;
409
410       style = (NILP (pgui_item->selected) || NILP (Feval (pgui_item->selected))
411                ? Qnil : pgui_item->style);
412
413       if (EQ (style, Qradio))
414         {
415           item_info.fType |= MFT_RADIOCHECK;
416           item_info.fState |= MFS_CHECKED;
417         }
418       else if (EQ (style, Qtoggle))
419         {
420           item_info.fState |= MFS_CHECKED;
421         }
422
423       id = allocate_menu_item_id (path, pgui_item->name,
424                                   pgui_item->suffix);
425       Fputhash (id, pgui_item->callback, hash_tab);
426
427       item_info.wID = (UINT) XINT (id);
428       item_info.fType |= MFT_STRING;
429       item_info.dwTypeData = displayable_menu_item (gui_item, bar_p, &accel);
430
431       if (accel && bar_p)
432         *accel_list = Fcons (make_char (accel), *accel_list);
433
434       UNGCPRO;
435     }
436   else
437     {
438       signal_simple_error ("Malformed menu item descriptor", item);
439     }
440
441   if (flush_right)
442     item_info.fType |= MFT_RIGHTJUSTIFY;
443
444   InsertMenuItem (menu, UINT_MAX, TRUE, &item_info);
445
446 done:;
447 }  
448
449 /*
450  * This function is called from populate_menu and checksum_menu.
451  * When called to populate, MENU is a menu handle, PATH is a
452  * list of strings representing menu path from root to this submenu,
453  * DESCRIPTOR is a menu descriptor, HASH_TAB is a hash table associated
454  * with root menu, BAR_P indicates whether this called for a menubar or
455  * a popup, and POPULATE_P is non-zero. Return value must be ignored.
456  * When called to checksum, DESCRIPTOR has the same meaning, POPULATE_P
457  * is zero, PATH must be Qnil, and the rest of parameters is ignored.
458  * Return value is the menu checksum.
459  */
460 static unsigned long
461 populate_or_checksum_helper (HMENU menu, Lisp_Object path, Lisp_Object desc,
462                              Lisp_Object hash_tab, int bar_p, int populate_p)
463 {
464   Lisp_Object item_desc;
465   int deep_p, flush_right;
466   struct gcpro gcpro1, gcpro2, gcpro3;
467   unsigned long checksum;
468   Lisp_Object gui_item = allocate_gui_item ();
469   Lisp_Object accel_list = Qnil;
470   Lisp_Gui_Item *pgui_item = XGUI_ITEM (gui_item);
471
472   GCPRO3 (gui_item, accel_list, desc);
473
474   /* We are sometimes called with the menubar unchanged, and with changed
475      right flush. We have to update the menubar in this case,
476      so account for the compliance setting in the hash value */
477   checksum = REPLACE_ME_WITH_GLOBAL_VARIABLE_WHICH_CONTROLS_RIGHT_FLUSH;
478
479   /* Will initially contain only "(empty)" */
480   if (populate_p)
481     empty_menu (menu, 1);
482
483   /* PATH set to nil indicates top-level popup or menubar */
484   deep_p = !NILP (path);
485
486   /* Fetch keywords prepending the item list */
487   desc = menu_parse_submenu_keywords (desc, gui_item);
488
489   /* Check that menu name is specified when expected */
490   if (NILP (pgui_item->name) && deep_p)
491     signal_simple_error ("Menu must have a name", desc);
492
493   /* Apply filter if specified */
494   if (!NILP (pgui_item->filter))
495     desc = call1 (pgui_item->filter, desc);
496
497   /* Loop thru the desc's CDR and add items for each entry */
498   flush_right = 0;
499   EXTERNAL_LIST_LOOP (item_desc, desc)
500     {
501       if (NILP (XCAR (item_desc)))
502         {
503           /* Do not flush right menubar items when MS style compliant */
504           if (bar_p && !REPLACE_ME_WITH_GLOBAL_VARIABLE_WHICH_CONTROLS_RIGHT_FLUSH)
505             flush_right = 1;
506           if (!populate_p)
507             checksum = HASH2 (checksum, LISP_HASH (Qnil));
508         }
509       else if (populate_p)
510         populate_menu_add_item (menu, path, hash_tab,
511                                 XCAR (item_desc), &accel_list,
512                                 flush_right, bar_p);
513       else
514         checksum = HASH2 (checksum,
515                           checksum_menu_item (XCAR (item_desc)));
516     }
517   
518   if (populate_p)
519     {
520       /* Remove the "(empty)" item, if there are other ones */
521       if (GetMenuItemCount (menu) > 1)
522         RemoveMenu (menu, EMPTY_ITEM_ID, MF_BYCOMMAND);
523
524       /* Add the header to the popup, if told so. The same as in X - an
525          insensitive item, and a separator (Seems to me, there were
526          two separators in X... In Windows this looks ugly, anyways.) */
527       if (!bar_p && !deep_p && popup_menu_titles && !NILP (pgui_item->name))
528         {
529           CHECK_STRING (pgui_item->name);
530           InsertMenu (menu, 0, MF_BYPOSITION | MF_STRING | MF_DISABLED,
531                       0, XSTRING_DATA(pgui_item->name));
532           InsertMenu (menu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);
533           SetMenuDefaultItem (menu, 0, MF_BYPOSITION);
534         }
535     }
536
537   if (bar_p)
538     Fputhash (Qt, accel_list, hash_tab);
539
540   UNGCPRO;
541   return checksum;
542 }
543
544 static void
545 populate_menu (HMENU menu, Lisp_Object path, Lisp_Object desc,
546                Lisp_Object hash_tab, int bar_p)
547 {
548   populate_or_checksum_helper (menu, path, desc, hash_tab, bar_p, 1);
549 }
550
551 static unsigned long
552 checksum_menu (Lisp_Object desc)
553 {
554   return populate_or_checksum_helper (NULL, Qnil, desc, Qunbound, 0, 0);
555 }
556
557 static void
558 update_frame_menubar_maybe (struct frame *f)
559 {
560   HMENU menubar = GetMenu (FRAME_MSWINDOWS_HANDLE (f));
561   struct window *w = XWINDOW (FRAME_LAST_NONMINIBUF_WINDOW (f));
562   Lisp_Object desc = (!NILP (w->menubar_visible_p)
563                       ? symbol_value_in_buffer (Qcurrent_menubar, w->buffer)
564                       : Qnil);
565   struct gcpro gcpro1;
566
567   GCPRO1 (desc); /* it's safest to do this, just in case some filter
568                     or something changes the value of current-menubar */
569
570   top_level_menu = menubar;
571
572   if (NILP (desc) && menubar != NULL)
573     {
574       /* Menubar has gone */
575       FRAME_MSWINDOWS_MENU_HASH_TABLE (f) = Qnil;
576       SetMenu (FRAME_MSWINDOWS_HANDLE (f), NULL);
577       DestroyMenu (menubar);
578       DrawMenuBar (FRAME_MSWINDOWS_HANDLE (f));
579       UNGCPRO;
580       return;
581     }
582
583   if (!NILP (desc) && menubar == NULL)
584     {
585       /* Menubar has appeared */
586       menubar = CreateMenu ();
587       goto populate;
588     }
589
590   if (NILP (desc))
591     {
592       /* We did not have the bar and are not going to */
593       UNGCPRO;
594       return;
595     }
596
597   /* Now we bail out if the menubar has not changed */
598   if (FRAME_MSWINDOWS_MENU_CHECKSUM (f) == checksum_menu (desc))
599     {
600       UNGCPRO;
601       return;
602     }
603
604 populate:
605   /* Come with empty hash table */
606   if (NILP (FRAME_MSWINDOWS_MENU_HASH_TABLE (f)))
607     FRAME_MSWINDOWS_MENU_HASH_TABLE (f) =
608       make_lisp_hash_table (50, HASH_TABLE_NON_WEAK, HASH_TABLE_EQUAL);
609   else
610     Fclrhash (FRAME_MSWINDOWS_MENU_HASH_TABLE (f));
611
612   Fputhash (hmenu_to_lisp_object (menubar), Qnil,
613             FRAME_MSWINDOWS_MENU_HASH_TABLE (f));
614   populate_menu (menubar, Qnil, desc,
615                  FRAME_MSWINDOWS_MENU_HASH_TABLE (f), 1);
616   SetMenu (FRAME_MSWINDOWS_HANDLE (f), menubar);
617   DrawMenuBar (FRAME_MSWINDOWS_HANDLE (f));
618
619   FRAME_MSWINDOWS_MENU_CHECKSUM (f) = checksum_menu (desc);
620
621   UNGCPRO;
622 }
623
624 static void
625 prune_menubar (struct frame *f)
626 {
627   HMENU menubar = GetMenu (FRAME_MSWINDOWS_HANDLE (f));
628   Lisp_Object desc = current_frame_menubar (f);
629   struct gcpro gcpro1;
630
631   if (menubar == NULL)
632     return;
633
634   /* #### If a filter function has set desc to Qnil, this abort()
635      triggers. To resolve, we must prevent filters explicitly from
636      mangling with the active menu. In apply_filter probably?
637      Is copy-tree on the whole menu too expensive? */
638   if (NILP (desc))
639     /* abort(); */
640     return;
641
642   GCPRO1 (desc); /* just to be safe -- see above */
643   /* We do the trick by removing all items and re-populating top level */
644   empty_menu (menubar, 0);
645
646   assert (HASH_TABLEP (FRAME_MSWINDOWS_MENU_HASH_TABLE (f)));
647   Fclrhash (FRAME_MSWINDOWS_MENU_HASH_TABLE (f));
648
649   Fputhash (hmenu_to_lisp_object (menubar), Qnil,
650             FRAME_MSWINDOWS_MENU_HASH_TABLE (f));
651   populate_menu (menubar, Qnil, desc, 
652                  FRAME_MSWINDOWS_MENU_HASH_TABLE (f), 1);
653   UNGCPRO;
654 }
655
656 /*
657  * This is called when cleanup is possible. It is better not to
658  * clean things up at all than do it too early!
659  */
660 static void
661 menu_cleanup (struct frame *f)
662 {
663   /* This function can GC */
664   current_menudesc = Qnil;
665   current_hash_table = Qnil;
666   prune_menubar (f);
667 }
668
669 int
670 mswindows_char_is_accelerator (struct frame *f, Emchar ch)
671 {
672   Lisp_Object hash = FRAME_MSWINDOWS_MENU_HASH_TABLE (f);
673
674   assert (HASH_TABLEP (hash));
675   /* !!#### not Mule-ized */
676   return !NILP (memq_no_quit (make_char (tolower (ch)),
677                               Fgethash (Qt, hash, Qnil)));
678 }
679   
680 \f
681 /*------------------------------------------------------------------------*/
682 /* Message handlers                                                       */
683 /*------------------------------------------------------------------------*/
684 static Lisp_Object
685 unsafe_handle_wm_initmenupopup_1 (HMENU menu, struct frame *f)
686 {
687   /* This function can call lisp, beat dogs and stick chewing gum to
688      everything! */
689
690   Lisp_Object path, desc;
691   struct gcpro gcpro1;
692
693   /* Find which guy is going to explode */
694   path = Fgethash (hmenu_to_lisp_object (menu), current_hash_table, Qunbound);
695   assert (!UNBOUNDP (path));
696 #ifdef DEBUG_XEMACS
697   /* Allow to continue in a debugger after assert - not so fatal */
698   if (UNBOUNDP (path))
699     error ("internal menu error");
700 #endif
701
702   /* Now find a desc chunk for it. If none, then probably menu open
703      hook has played too much games around stuff */
704   desc = Fmenu_find_real_submenu (current_menudesc, path);
705   if (NILP (desc))
706     signal_simple_error ("This menu does not exist any more", path);
707
708   /* Now, stuff it */
709   /* DESC may be generated by filter, so we have to gcpro it */
710   GCPRO1 (desc);
711   populate_menu (menu, path, desc, current_hash_table, 0);
712   UNGCPRO;
713   return Qt;
714 }
715
716 static Lisp_Object
717 unsafe_handle_wm_initmenu_1 (struct frame *f)
718 {
719   /* This function can call lisp */
720
721   /* NOTE: This is called for the bar only, WM_INITMENU
722      for popups is filtered out */
723
724   /* #### - this menubar update mechanism is expensively anti-social and
725      the activate-menubar-hook is now mostly obsolete. */
726
727   /* We simply ignore return value. In any case, we construct the bar
728      on the fly */
729   run_hook (Qactivate_menubar_hook);
730
731   update_frame_menubar_maybe (f);
732
733   current_menudesc = current_frame_menubar (f);
734   current_hash_table = FRAME_MSWINDOWS_MENU_HASH_TABLE (f);
735   assert (HASH_TABLEP (current_hash_table));
736
737   return Qt;
738 }
739
740 /*
741  * Return value is Qt if we have dispatched the command,
742  * or Qnil if id has not been mapped to a callback.
743  * Window procedure may try other targets to route the
744  * command if we return nil
745  */
746 Lisp_Object
747 mswindows_handle_wm_command (struct frame *f, WORD id)
748 {
749   /* Try to map the command id through the proper hash table */
750   Lisp_Object data, fn, arg, frame;
751   struct gcpro gcpro1;
752
753   if (NILP (current_hash_table))
754     return Qnil;
755
756   data = Fgethash (make_int (id), current_hash_table, Qunbound);
757
758   if (UNBOUNDP (data))
759     {
760       menu_cleanup (f);
761       return Qnil;
762     }
763
764   /* Need to gcpro because the hash table may get destroyed by
765      menu_cleanup(), and will not gcpro the data any more */
766   GCPRO1 (data);
767   menu_cleanup (f);
768
769   /* Ok, this is our one. Enqueue it. */
770   get_gui_callback (data, &fn, &arg);
771   XSETFRAME (frame, f);
772   /* this used to call mswindows_enqueue_misc_user_event but that
773      breaks customize because the misc_event gets eval'ed in some
774      cicumstances. Don't change it back unless you can fix the
775      customize problem also.*/
776   enqueue_misc_user_event (frame, fn, arg);
777   mswindows_enqueue_magic_event (NULL, XM_BUMPQUEUE);
778
779   UNGCPRO; /* data */
780   return Qt;
781 }
782
783 \f
784 /*------------------------------------------------------------------------*/
785 /* Message handling proxies                                               */
786 /*------------------------------------------------------------------------*/
787
788 static HMENU wm_initmenu_menu;
789 static struct frame *wm_initmenu_frame;
790
791 static Lisp_Object
792 unsafe_handle_wm_initmenupopup (Lisp_Object u_n_u_s_e_d)
793 {
794   return unsafe_handle_wm_initmenupopup_1 (wm_initmenu_menu, wm_initmenu_frame);
795 }
796
797 static Lisp_Object
798 unsafe_handle_wm_initmenu (Lisp_Object u_n_u_s_e_d)
799 {
800   return unsafe_handle_wm_initmenu_1 (wm_initmenu_frame);
801 }
802
803 Lisp_Object
804 mswindows_handle_wm_initmenupopup (HMENU hmenu, struct frame *frm)
805 {
806   /* We cannot pass hmenu as a lisp object. Use static var */
807   wm_initmenu_menu = hmenu;
808   wm_initmenu_frame = frm;
809   return mswindows_protect_modal_loop (unsafe_handle_wm_initmenupopup, Qnil);
810 }
811
812 Lisp_Object
813 mswindows_handle_wm_initmenu (HMENU hmenu, struct frame *f)
814 {
815   /* Handle only frame menubar, ignore if from popup or system menu */
816   if (GetMenu (FRAME_MSWINDOWS_HANDLE (f)) == hmenu)
817     {
818       wm_initmenu_frame = f;
819       return mswindows_protect_modal_loop (unsafe_handle_wm_initmenu, Qnil);
820     }
821   return Qt;
822 }
823
824 \f
825 /*------------------------------------------------------------------------*/
826 /* Methods                                                                */
827 /*------------------------------------------------------------------------*/
828
829 static void
830 mswindows_update_frame_menubars (struct frame *f)
831 {
832   update_frame_menubar_maybe (f);
833 }
834
835 static void
836 mswindows_free_frame_menubars (struct frame *f)
837 {
838   FRAME_MSWINDOWS_MENU_HASH_TABLE (f) = Qnil;
839 }
840
841 static void
842 mswindows_popup_menu (Lisp_Object menu_desc, Lisp_Object event)
843 {
844   struct frame *f = selected_frame ();
845   Lisp_Event *eev = NULL;
846   HMENU menu;
847   POINT pt;
848   int ok;
849   struct gcpro gcpro1;
850
851   GCPRO1 (menu_desc); /* to be safe -- see above */
852
853   if (!NILP (event))
854     {
855       CHECK_LIVE_EVENT (event);
856       eev = XEVENT (event);
857       if (eev->event_type != button_press_event
858           && eev->event_type != button_release_event)
859         wrong_type_argument (Qmouse_event_p, event);
860     }
861   else if (!NILP (Vthis_command_keys))
862     {
863       /* if an event wasn't passed, use the last event of the event sequence
864          currently being executed, if that event is a mouse event */
865       eev = XEVENT (Vthis_command_keys); /* last event first */
866       if (eev->event_type != button_press_event
867           && eev->event_type != button_release_event)
868         eev = NULL;
869     }
870
871   /* Default is to put the menu at the point (10, 10) in frame */
872   if (eev)
873     {
874       pt.x = eev->event.button.x;
875       pt.y = eev->event.button.y;
876       ClientToScreen (FRAME_MSWINDOWS_HANDLE (f), &pt);
877     }
878   else
879     pt.x = pt.y = 10;
880
881   if (SYMBOLP (menu_desc))
882     menu_desc = Fsymbol_value (menu_desc);
883   CHECK_CONS (menu_desc);
884   CHECK_STRING (XCAR (menu_desc));
885
886   current_menudesc = menu_desc;
887   current_hash_table =
888     make_lisp_hash_table (10, HASH_TABLE_NON_WEAK, HASH_TABLE_EQUAL);
889   menu = create_empty_popup_menu ();
890   Fputhash (hmenu_to_lisp_object (menu), Qnil, current_hash_table);
891   top_level_menu = menu;
892   
893   /* see comments in menubar-x.c */
894   if (zmacs_regions)
895     zmacs_region_stays = 1;
896   
897   ok = TrackPopupMenu (menu,
898                        TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON,
899                        pt.x, pt.y, 0,
900                        FRAME_MSWINDOWS_HANDLE (f), NULL);
901
902   DestroyMenu (menu);
903
904   /* Signal a signal if caught by Track...() modal loop */
905   mswindows_unmodalize_signal_maybe ();
906
907   /* This is probably the only real reason for failure */
908   if (!ok)
909     {
910       menu_cleanup (f);
911       signal_simple_error ("Cannot track popup menu while in menu",
912                            menu_desc);
913     }
914   UNGCPRO;
915 }
916
917 \f
918 /*------------------------------------------------------------------------*/
919 /* Initialization                                                         */
920 /*------------------------------------------------------------------------*/
921 void
922 syms_of_menubar_mswindows (void)
923 {
924 }
925
926 void
927 console_type_create_menubar_mswindows (void)
928 {
929   CONSOLE_HAS_METHOD (mswindows, update_frame_menubars);
930   CONSOLE_HAS_METHOD (mswindows, free_frame_menubars);
931   CONSOLE_HAS_METHOD (mswindows, popup_menu);
932 }
933
934 void
935 vars_of_menubar_mswindows (void)
936 {
937   current_menudesc = Qnil;
938   current_hash_table = Qnil;
939
940   staticpro (&current_menudesc);
941   staticpro (&current_hash_table);
942 }