Add some addresses for rmail users to ignore.
[emacs.git] / src / nsmenu.m
blobb3c56809733e0b1de0676a43fdba98fb86b67e0c
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007, 2008, 2009 Free Software Foundation, Inc.
4 This file is part of GNU Emacs.
6 GNU Emacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 GNU Emacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
20 By Adrian Robert, based on code from original nsmenu.m (Carl Edman,
21 Christian Limpach, Scott Bender, Christophe de Dinechin) and code in the
22 Carbon version by Yamamoto Mitsuharu. */
24 /* This should be the first include, as it may set up #defines affecting
25    interpretation of even the system includes. */
26 #include "config.h"
28 #include "lisp.h"
29 #include "window.h"
30 #include "buffer.h"
31 #include "keymap.h"
32 #include "coding.h"
33 #include "commands.h"
34 #include "blockinput.h"
35 #include "nsterm.h"
36 #include "termhooks.h"
37 #include "keyboard.h"
39 #define NSMENUPROFILE 0
41 #if NSMENUPROFILE
42 #include <sys/timeb.h>
43 #include <sys/types.h>
44 #endif
46 #define MenuStagger 10.0
48 #if 0
49 int menu_trace_num = 0;
50 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
51                                 __FILE__, __LINE__, ++menu_trace_num)
52 #else
53 #define NSTRACE(x)
54 #endif
56 #if 0
57 /* Include lisp -> C common menu parsing code */
58 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
59 #include "nsmenu_common.c"
60 #endif
62 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
63 extern Lisp_Object QCtoggle, QCradio;
65 extern Lisp_Object Vmenu_updating_frame;
67 Lisp_Object Qdebug_on_next_call;
68 extern Lisp_Object Voverriding_local_map, Voverriding_local_map_menu_flag,
69                    Qoverriding_local_map, Qoverriding_terminal_local_map;
71 extern long context_menu_value;
72 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
74 /* Nonzero means a menu is currently active.  */
75 static int popup_activated_flag;
77 /* NOTE: toolbar implementation is at end,
78   following complete menu implementation. */
81 /* ==========================================================================
83     Menu: Externally-called functions
85    ========================================================================== */
88 /* FIXME: not currently used, but should normalize with other terms. */
89 void
90 x_activate_menubar (struct frame *f)
92     fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
96 /* Supposed to discard menubar and free storage.  Since we share the
97    menubar among frames and update its context for the focused window,
98    there is nothing to do here. */
99 void
100 free_frame_menubar (struct frame *f)
102   return;
107 popup_activated ()
109   return popup_activated_flag;
113 /* --------------------------------------------------------------------------
114     Update menubar.  Three cases:
115     1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
116        just top-level menu strings (OS X), or goto case (2) (GNUstep).
117     2) deep_p = 1, submenu = nil: Recompute all submenus.
118     3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
119    -------------------------------------------------------------------------- */
120 void
121 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
123   NSAutoreleasePool *pool;
124   id menu = [NSApp mainMenu];
125   static EmacsMenu *last_submenu = nil;
126   BOOL needsSet = NO;
127   const char *submenuTitle = [[submenu title] UTF8String];
128   extern int waiting_for_input;
129   int owfi;
130   Lisp_Object items;
131   widget_value *wv, *first_wv, *prev_wv = 0;
132   int i;
134 #if NSMENUPROFILE
135   struct timeb tb;
136   long t;
137 #endif
139   NSTRACE (set_frame_menubar);
141   if (f != SELECTED_FRAME ())
142       return;
143   XSETFRAME (Vmenu_updating_frame, f);
144 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
146   BLOCK_INPUT;
147   pool = [[NSAutoreleasePool alloc] init];
149   /* Menu may have been created automatically; if so, discard it. */
150   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
151     {
152       [menu release];
153       menu = nil;
154     }
156   if (menu == nil)
157     {
158       menu = [[EmacsMenu alloc] initWithTitle: @"Emacs"];
159       needsSet = YES;
160     }
161   else
162     {  /* close up anything on there */
163       id attMenu = [menu attachedMenu];
164       if (attMenu != nil)
165         [attMenu close];
166     }
168 #if NSMENUPROFILE
169   ftime (&tb);
170   t = -(1000*tb.time+tb.millitm);
171 #endif
173   /* widget_value is a straightforward object translation of emacs's
174      Byzantine lisp menu structures */
175   wv = xmalloc_widget_value ();
176   wv->name = "Emacs";
177   wv->value = 0;
178   wv->enabled = 1;
179   wv->button_type = BUTTON_TYPE_NONE;
180   wv->help = Qnil;
181   first_wv = wv;
183 #ifdef NS_IMPL_GNUSTEP
184   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
185 #endif
187   if (deep_p)
188     {
189       /* Fully parse one or more of the submenus. */
190       int n = 0;
191       int *submenu_start, *submenu_end;
192       int *submenu_top_level_items, *submenu_n_panes;
193       struct buffer *prev = current_buffer;
194       Lisp_Object buffer;
195       int specpdl_count = SPECPDL_INDEX ();
196       int previous_menu_items_used = f->menu_bar_items_used;
197       Lisp_Object *previous_items
198         = (Lisp_Object *) alloca (previous_menu_items_used
199                                   * sizeof (Lisp_Object));
201       /* lisp preliminaries */
202       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
203       specbind (Qinhibit_quit, Qt);
204       specbind (Qdebug_on_next_call, Qnil);
205       record_unwind_save_match_data ();
206       if (NILP (Voverriding_local_map_menu_flag))
207         {
208           specbind (Qoverriding_terminal_local_map, Qnil);
209           specbind (Qoverriding_local_map, Qnil);
210         }
211       set_buffer_internal_1 (XBUFFER (buffer));
213       /* TODO: for some reason this is not needed in other terms,
214            but some menu updates call Info-extract-pointer which causes
215            abort-on-error if waiting-for-input.  Needs further investigation. */
216       owfi = waiting_for_input;
217       waiting_for_input = 0;
219       /* lucid hook and possible reset */
220       safe_run_hooks (Qactivate_menubar_hook);
221       if (! NILP (Vlucid_menu_bar_dirty_flag))
222         call0 (Qrecompute_lucid_menubar);
223       safe_run_hooks (Qmenu_bar_update_hook);
224       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
226       /* Now ready to go */
227       items = FRAME_MENU_BAR_ITEMS (f);
229       /* Save the frame's previous menu bar contents data */
230       if (previous_menu_items_used)
231         bcopy (XVECTOR (f->menu_bar_vector)->contents, previous_items,
232                previous_menu_items_used * sizeof (Lisp_Object));
234       /* parse stage 1: extract from lisp */
235       save_menu_items ();
237       menu_items = f->menu_bar_vector;
238       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
239       submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
240       submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
241       submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
242       submenu_top_level_items
243         = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
244       init_menu_items ();
245       for (i = 0; i < XVECTOR (items)->size; i += 4)
246         {
247           Lisp_Object key, string, maps;
249           key = XVECTOR (items)->contents[i];
250           string = XVECTOR (items)->contents[i + 1];
251           maps = XVECTOR (items)->contents[i + 2];
252           if (NILP (string))
253             break;
255           /* FIXME: we'd like to only parse the needed submenu, but this
256                was causing crashes in the _common parsing code.. need to make
257                sure proper initialization done.. */
258 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
259              continue; */
261           submenu_start[i] = menu_items_used;
263           menu_items_n_panes = 0;
264           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
265           submenu_n_panes[i] = menu_items_n_panes;
266           submenu_end[i] = menu_items_used;
267           n++;
268         }
270       finish_menu_items ();
271       waiting_for_input = owfi;
274       if (submenu && n == 0)
275         {
276           /* should have found a menu for this one but didn't */
277           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
278                   submenuTitle);
279           discard_menu_items ();
280           unbind_to (specpdl_count, Qnil);
281           [pool release];
282           UNBLOCK_INPUT;
283           return;
284         }
286       /* parse stage 2: insert into lucid 'widget_value' structures
287          [comments in other terms say not to evaluate lisp code here] */
288       wv = xmalloc_widget_value ();
289       wv->name = "menubar";
290       wv->value = 0;
291       wv->enabled = 1;
292       wv->button_type = BUTTON_TYPE_NONE;
293       wv->help = Qnil;
294       first_wv = wv;
296       for (i = 0; i < 4*n; i += 4)
297         {
298           menu_items_n_panes = submenu_n_panes[i];
299           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
300                                       submenu_top_level_items[i]);
301           if (prev_wv)
302             prev_wv->next = wv;
303           else
304             first_wv->contents = wv;
305           /* Don't set wv->name here; GC during the loop might relocate it.  */
306           wv->enabled = 1;
307           wv->button_type = BUTTON_TYPE_NONE;
308           prev_wv = wv;
309         }
311       set_buffer_internal_1 (prev);
313       /* Compare the new menu items with previous, and leave off if no change */
314       /* FIXME: following other terms here, but seems like this should be
315            done before parse stage 2 above, since its results aren't used */
316       if (previous_menu_items_used
317           && (!submenu || (submenu && submenu == last_submenu))
318           && menu_items_used == previous_menu_items_used)
319         {
320           for (i = 0; i < previous_menu_items_used; i++)
321             /* FIXME: this ALWAYS fails on Buffers menu items.. something
322                  about their strings causes them to change every time, so we
323                  double-check failures */
324             if (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i]))
325               if (!(STRINGP (previous_items[i])
326                     && STRINGP (XVECTOR (menu_items)->contents[i])
327                     && !strcmp (SDATA (previous_items[i]),
328                                SDATA (XVECTOR (menu_items)->contents[i]))))
329                   break;
330           if (i == previous_menu_items_used)
331             {
332               /* No change.. */
334 #if NSMENUPROFILE
335               ftime (&tb);
336               t += 1000*tb.time+tb.millitm;
337               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
338 #endif
340               free_menubar_widget_value_tree (first_wv);
341               discard_menu_items ();
342               unbind_to (specpdl_count, Qnil);
343               [pool release];
344               UNBLOCK_INPUT;
345               return;
346             }
347         }
348       /* The menu items are different, so store them in the frame */
349       /* FIXME: this is not correct for single-submenu case */
350       f->menu_bar_vector = menu_items;
351       f->menu_bar_items_used = menu_items_used;
353       /* Calls restore_menu_items, etc., as they were outside */
354       unbind_to (specpdl_count, Qnil);
356       /* Parse stage 2a: now GC cannot happen during the lifetime of the
357          widget_value, so it's safe to store data from a Lisp_String */
358       wv = first_wv->contents;
359       for (i = 0; i < XVECTOR (items)->size; i += 4)
360         {
361           Lisp_Object string;
362           string = XVECTOR (items)->contents[i + 1];
363           if (NILP (string))
364             break;
365 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
366                continue; */
368           wv->name = (char *) SDATA (string);
369           update_submenu_strings (wv->contents);
370           wv = wv->next;
371         }
373       /* Now, update the NS menu; if we have a submenu, use that, otherwise
374          create a new menu for each sub and fill it. */
375       if (submenu)
376         {
377           for (wv = first_wv->contents; wv; wv = wv->next)
378             {
379               if (!strcmp (submenuTitle, wv->name))
380                 {
381                   [submenu fillWithWidgetValue: wv->contents];
382                   last_submenu = submenu;
383                   break;
384                 }
385             }
386         }
387       else
388         {
389           [menu fillWithWidgetValue: first_wv->contents];
390         }
392     }
393   else
394     {
395       static int n_previous_strings = 0;
396       static char previous_strings[100][10];
397       static struct frame *last_f = NULL;
398       int n;
399       Lisp_Object string;
401       /* Make widget-value tree w/ just the top level menu bar strings */
402       items = FRAME_MENU_BAR_ITEMS (f);
403       if (NILP (items))
404         {
405           [pool release];
406           UNBLOCK_INPUT;
407           return;
408         }
411       /* check if no change.. this mechanism is a bit rough, but ready */
412       n = XVECTOR (items)->size / 4;
413       if (f == last_f && n_previous_strings == n)
414         {
415           for (i = 0; i<n; i++)
416             {
417               string = AREF (items, 4*i+1);
419               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
420                 continue;
421               if (NILP (string))
422                 if (previous_strings[i][0])
423                   break;
424               else
425                 continue;
426               if (strncmp (previous_strings[i], SDATA (string), 10))
427                 break;
428             }
430           if (i == n)
431             {
432               [pool release];
433               UNBLOCK_INPUT;
434               return;
435             }
436         }
438       [menu clear];
439       for (i = 0; i < XVECTOR (items)->size; i += 4)
440         {
441           string = XVECTOR (items)->contents[i + 1];
442           if (NILP (string))
443             break;
445           if (n < 100)
446             strncpy (previous_strings[i/4], SDATA (string), 10);
448           wv = xmalloc_widget_value ();
449           wv->name = (char *) SDATA (string);
450           wv->value = 0;
451           wv->enabled = 1;
452           wv->button_type = BUTTON_TYPE_NONE;
453           wv->help = Qnil;
454           wv->call_data = (void *) (EMACS_INT) (-1);
456 #ifdef NS_IMPL_COCOA
457           /* we'll update the real copy under app menu when time comes */
458           if (!strcmp ("Services", wv->name))
459             {
460               /* but we need to make sure it will update on demand */
461               [svcsMenu setFrame: f];
462               [svcsMenu setDelegate: svcsMenu];
463             }
464           else
465 #endif
466           [menu addSubmenuWithTitle: wv->name forFrame: f];
468           if (prev_wv)
469             prev_wv->next = wv;
470           else
471             first_wv->contents = wv;
472           prev_wv = wv;
473         }
475       last_f = f;
476       if (n < 100)
477         n_previous_strings = n;
478       else
479         n_previous_strings = 0;
481     }
482   free_menubar_widget_value_tree (first_wv);
485 #if NSMENUPROFILE
486   ftime (&tb);
487   t += 1000*tb.time+tb.millitm;
488   fprintf (stderr, "Menu update took %ld msec.\n", t);
489 #endif
491   /* set main menu */
492   if (needsSet)
493     [NSApp setMainMenu: menu];
495   [pool release];
496   UNBLOCK_INPUT;
501 /* Main emacs core entry point for menubar menus: called to indicate that the
502    frame's menus have changed, and the *step representation should be updated
503    from Lisp. */
504 void
505 set_frame_menubar (struct frame *f, int first_time, int deep_p)
507   ns_update_menubar (f, deep_p, nil);
511 /* Utility (from macmenu.c): is this item a separator? */
512 static int
513 name_is_separator (name)
514      const char *name;
516   const char *start = name;
518   /* Check if name string consists of only dashes ('-').  */
519   while (*name == '-') name++;
520   /* Separators can also be of the form "--:TripleSuperMegaEtched"
521      or "--deep-shadow".  We don't implement them yet, se we just treat
522      them like normal separators.  */
523   return (*name == '\0' || start + 2 == name);
527 /* ==========================================================================
529     Menu: class implementation
531    ========================================================================== */
534 /* Menu that can define itself from Emacs "widget_value"s and will lazily
535    update itself when user clicked.  Based on Carbon/AppKit implementation
536    by Yamamoto Mitsuharu. */
537 @implementation EmacsMenu
539 /* override designated initializer */
540 - initWithTitle: (NSString *)title
542   if (self = [super initWithTitle: title])
543     [self setAutoenablesItems: NO];
544   return self;
548 /* used for top-level */
549 - initWithTitle: (NSString *)title frame: (struct frame *)f
551   [self initWithTitle: title];
552   frame = f;
553 #ifdef NS_IMPL_COCOA
554   [self setDelegate: self];
555 #endif
556   return self;
560 - (void)setFrame: (struct frame *)f
562   frame = f;
566 /* delegate method called when a submenu is being opened: run a 'deep' call
567    to set_frame_menubar */
568 - (void)menuNeedsUpdate: (NSMenu *)menu
570   NSEvent *event = [[FRAME_NS_VIEW (frame) window] currentEvent];
571   /* HACK: Cocoa/Carbon will request update on every keystroke
572      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
573      since key equivalents are handled through emacs.
574      On Leopard, even keystroke events generate SystemDefined events, but
575      their subtype is 8. */
576   if ([event type] != NSSystemDefined || [event subtype] == 8)
577     return;
578 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
579   ns_update_menubar (frame, 1, self);
583 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
585   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
586       && FRAME_NS_VIEW (SELECTED_FRAME ()))
587     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
588   return YES;
592 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
593    into an accelerator string.  We are only able to display a single character
594    for an accelerator, together with an optional modifier combination.  (Under
595    Carbon more control was possible, but in Cocoa multi-char strings passed to
596    NSMenuItem get ignored.  For now we try to display a super-single letter
597    combo, and return the others as strings to be appended to the item title.
598    (This is signaled by setting keyEquivModMask to 0 for now.) */
599 -(NSString *)parseKeyEquiv: (char *)key
601   char *tpos = key;
602   keyEquivModMask = NSCommandKeyMask;
604   if (!key || !strlen (key))
605     return @"";
606   
607   while (*tpos == ' ' || *tpos == '(')
608     tpos++;
609   if (*tpos != 's') {
610     keyEquivModMask = 0; /* signal */
611     return [NSString stringWithUTF8String: tpos];
612   }
613   return [NSString stringWithFormat: @"%c", tpos[2]];
617 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
619   NSMenuItem *item;
620   widget_value *wv = (widget_value *)wvptr;
622   if (name_is_separator (wv->name))
623     {
624       item = [NSMenuItem separatorItem];
625       [self addItem: item];
626     }
627   else
628     {
629       NSString *title, *keyEq;
630       title = [NSString stringWithUTF8String: wv->name];
631       if (title == nil)
632         title = @"< ? >";  /* (get out in the open so we know about it) */
634       keyEq = [self parseKeyEquiv: wv->key];
635       if (keyEquivModMask == 0)
636         title = [title stringByAppendingFormat: @" (%@)", keyEq];
638       item = [self addItemWithTitle: (NSString *)title
639                              action: @selector (menuDown:)
640                       keyEquivalent: keyEq];
641       [item setKeyEquivalentModifierMask: keyEquivModMask];
643       [item setEnabled: wv->enabled];
645       /* Draw radio buttons and tickboxes */
646       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
647                            wv->button_type == BUTTON_TYPE_RADIO))
648         [item setState: NSOnState];
649       else
650         [item setState: NSOffState];
652       [item setTag: (int)wv->call_data];
653     }
655   return item;
659 /* convenience */
660 -(void) clear
662   int n;
663   
664   for (n = [self numberOfItems]-1; n >= 0; n--)
665     {
666       NSMenuItem *item = [self itemAtIndex: n];
667       NSString *title = [item title];
668       if (([title length] == 0 || [@"Apple" isEqualToString: title])
669           && ![item isSeparatorItem])
670         continue;
671       [self removeItemAtIndex: n];
672     }
676 - (void)fillWithWidgetValue: (void *)wvptr
678   widget_value *wv = (widget_value *)wvptr;
680   /* clear existing contents */
681   [self setMenuChangedMessagesEnabled: NO];
682   [self clear];
684   /* add new contents */
685   for (; wv != NULL; wv = wv->next)
686     {
687       NSMenuItem *item = [self addItemWithWidgetValue: wv];
689       if (wv->contents)
690         {
691           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: @"Submenu"];
693           [self setSubmenu: submenu forItem: item];
694           [submenu fillWithWidgetValue: wv->contents];
695           [submenu release];
696           [item setAction: nil];
697         }
698     }
700   [self setMenuChangedMessagesEnabled: YES];
701 #ifdef NS_IMPL_GNUSTEP
702   if ([[self window] isVisible])
703     [self sizeToFit];
704 #else
705   if ([self supermenu] == nil)
706     [self sizeToFit];
707 #endif
711 /* adds an empty submenu and returns it */
712 - (EmacsMenu *)addSubmenuWithTitle: (char *)title forFrame: (struct frame *)f
714   NSString *titleStr = [NSString stringWithUTF8String: title];
715   NSMenuItem *item = [self addItemWithTitle: titleStr
716                                      action: nil /*@selector (menuDown:) */
717                               keyEquivalent: @""];
718   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
719   [self setSubmenu: submenu forItem: item];
720   [submenu release];
721   return submenu;
724 /* run a menu in popup mode */
725 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
726                  keymaps: (int)keymaps
728   EmacsView *view = FRAME_NS_VIEW (f);
729 /*   p = [view convertPoint:p fromView: nil]; */
730   p.y = NSHeight ([view frame]) - p.y;
731   NSEvent *e = [[view window] currentEvent];
732   NSEvent *event = [NSEvent mouseEventWithType: NSRightMouseDown
733                                       location: p
734                                  modifierFlags: 0
735                                      timestamp: [e timestamp]
736                                   windowNumber: [[view window] windowNumber]
737                                        context: [e context]
738                                    eventNumber: 0/*[e eventNumber] */
739                                     clickCount: 1
740                                       pressure: 0];
741   long retVal;
743   context_menu_value = -1;
744   [NSMenu popUpContextMenu: self withEvent: event forView: view];
745   retVal = context_menu_value;
746   context_menu_value = 0;
747   return retVal > 0
748       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
749       : Qnil;
752 @end  /* EmacsMenu */
756 /* ==========================================================================
758     Context Menu: implementing functions
760    ========================================================================== */
762 static Lisp_Object
763 cleanup_popup_menu (Lisp_Object arg)
765   discard_menu_items ();
766   return Qnil;
770 static Lisp_Object
771 ns_popup_menu (Lisp_Object position, Lisp_Object menu)
773   EmacsMenu *pmenu;
774   struct frame *f = NULL;
775   NSPoint p;
776   Lisp_Object window, x, y, tem, keymap, title;
777   struct gcpro gcpro1;
778   int specpdl_count = SPECPDL_INDEX (), specpdl_count2;
779   char *error_name = NULL;
780   int keymaps = 0;
781   widget_value *wv, *first_wv = 0;
783   NSTRACE (ns_popup_menu);
785   if (!NILP (position))
786     {
787       check_ns ();
788   
789       if (EQ (position, Qt)
790           || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
791                                    || EQ (XCAR (position), Qtool_bar))))
792         {
793           /* Use the mouse's current position.  */
794           struct frame *new_f = SELECTED_FRAME ();
796           if (FRAME_TERMINAL (new_f)->mouse_position_hook)
797             (*FRAME_TERMINAL (new_f)->mouse_position_hook)
798               (&new_f, 0, 0, 0, &x, &y, 0);
799           if (new_f != 0)
800             XSETFRAME (window, new_f);
801           else
802             {
803               window = selected_window;
804               x = make_number (0);
805               y = make_number (0);
806             }
807         }
808       else
809         {
810           CHECK_CONS (position);
811           tem = Fcar (position);
812           if (XTYPE (tem) == Lisp_Cons)
813             {
814               window = Fcar (Fcdr (position));
815               x = Fcar (tem);
816               y = Fcar (Fcdr (tem));
817             }
818           else
819             {
820               tem = Fcar (Fcdr (position));
821               window = Fcar (tem);
822               tem = Fcar (Fcdr (Fcdr (tem)));
823               x = Fcar (tem);
824               y = Fcdr (tem);
825             }
826         }
827   
828       CHECK_NUMBER (x);
829       CHECK_NUMBER (y);
831       if (FRAMEP (window))
832         {
833           f = XFRAME (window);
834       
835           p.x = 0;
836           p.y = 0;
837         }
838       else
839         {
840           struct window *win = XWINDOW (window);
841           CHECK_LIVE_WINDOW (window);
842           f = XFRAME (WINDOW_FRAME (win));
843           p.x = FRAME_COLUMN_WIDTH (f) * WINDOW_LEFT_EDGE_COL (win);
844           p.y = FRAME_LINE_HEIGHT (f) * WINDOW_TOP_EDGE_LINE (win);
845         }
847       p.x += XINT (x); p.y += XINT (y);
849       XSETFRAME (Vmenu_updating_frame, f);
850     }
851   else
852     {      /* no position given */
853       /* FIXME: if called during dump, we need to stop precomputation of
854          key equivalents (see below) because the keydefs in ns-win.el have
855          not been loaded yet. */
856       if (noninteractive)
857         return Qnil;
858       Vmenu_updating_frame = Qnil;
859     }
861   /* now parse the lisp menus */
862   record_unwind_protect (unuse_menu_items, Qnil);
863   title = Qnil;
864   GCPRO1 (title);
866   /* Decode the menu items from what was specified.  */
868   keymap = get_keymap (menu, 0, 0);
869   if (CONSP (keymap))
870     {
871       /* We were given a keymap.  Extract menu info from the keymap.  */
872       Lisp_Object prompt;
874       /* Extract the detailed info to make one pane.  */
875       keymap_panes (&menu, 1, NILP (position));
877       /* Search for a string appearing directly as an element of the keymap.
878          That string is the title of the menu.  */
879       prompt = Fkeymap_prompt (keymap);
880       title = NILP (prompt) ? build_string ("Select") : prompt;
882       /* Make that be the pane title of the first pane.  */
883       if (!NILP (prompt) && menu_items_n_panes >= 0)
884         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt;
886       keymaps = 1;
887     }
888   else if (CONSP (menu) && KEYMAPP (XCAR (menu)))
889     {
890       /* We were given a list of keymaps.  */
891       int nmaps = XFASTINT (Flength (menu));
892       Lisp_Object *maps
893         = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object));
894       int i;
896       title = Qnil;
898       /* The first keymap that has a prompt string
899          supplies the menu title.  */
900       for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem))
901         {
902           Lisp_Object prompt;
904           maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0);
906           prompt = Fkeymap_prompt (keymap);
907           if (NILP (title) && !NILP (prompt))
908             title = prompt;
909         }
911       /* Extract the detailed info to make one pane.  */
912       keymap_panes (maps, nmaps, NILP (position));
914       /* Make the title be the pane title of the first pane.  */
915       if (!NILP (title) && menu_items_n_panes >= 0)
916         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title;
918       keymaps = 1;
919     }
920   else
921     {
922       /* We were given an old-fashioned menu.  */
923       title = Fcar (menu);
924       CHECK_STRING (title);
926       list_of_panes (Fcdr (menu));
928       keymaps = 0;
929     }
931   unbind_to (specpdl_count, Qnil);
933   /* If no position given, that was a signal to just precompute and cache
934      key equivalents, which was a side-effect of what we just did. */
935   if (NILP (position))
936     {
937       discard_menu_items ();
938       UNGCPRO;
939       return Qnil;
940     }
942   record_unwind_protect (cleanup_popup_menu, Qnil);
943   BLOCK_INPUT;
945   /* now parse stage 2 as in ns_update_menubar */
946   wv = xmalloc_widget_value ();
947   wv->name = "contextmenu";
948   wv->value = 0;
949   wv->enabled = 1;
950   wv->button_type = BUTTON_TYPE_NONE;
951   wv->help = Qnil;
952   first_wv = wv;
954   specpdl_count2 = SPECPDL_INDEX ();
956 #if 0
957   /* FIXME: a couple of one-line differences prevent reuse */
958   wv = digest_single_submenu (0, menu_items_used, Qnil);
959 #else
960   {
961   widget_value *save_wv = 0, *prev_wv = 0;
962   widget_value **submenu_stack
963     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
964 /*   Lisp_Object *subprefix_stack
965        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
966   int submenu_depth = 0;
967   int first_pane = 1;
968   int i;
970   /* Loop over all panes and items, filling in the tree.  */
971   i = 0;
972   while (i < menu_items_used)
973     {
974       if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
975         {
976           submenu_stack[submenu_depth++] = save_wv;
977           save_wv = prev_wv;
978           prev_wv = 0;
979           first_pane = 1;
980           i++;
981         }
982       else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
983         {
984           prev_wv = save_wv;
985           save_wv = submenu_stack[--submenu_depth];
986           first_pane = 0;
987           i++;
988         }
989       else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
990                && submenu_depth != 0)
991         i += MENU_ITEMS_PANE_LENGTH;
992       /* Ignore a nil in the item list.
993          It's meaningful only for dialog boxes.  */
994       else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
995         i += 1;
996       else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
997         {
998           /* Create a new pane.  */
999           Lisp_Object pane_name, prefix;
1000           char *pane_string;
1002           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
1003           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
1005 #ifndef HAVE_MULTILINGUAL_MENU
1006           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
1007             {
1008               pane_name = ENCODE_MENU_STRING (pane_name);
1009               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
1010             }
1011 #endif
1012           pane_string = (NILP (pane_name)
1013                          ? "" : (char *) SDATA (pane_name));
1014           /* If there is just one top-level pane, put all its items directly
1015              under the top-level menu.  */
1016           if (menu_items_n_panes == 1)
1017             pane_string = "";
1019           /* If the pane has a meaningful name,
1020              make the pane a top-level menu item
1021              with its items as a submenu beneath it.  */
1022           if (!keymaps && strcmp (pane_string, ""))
1023             {
1024               wv = xmalloc_widget_value ();
1025               if (save_wv)
1026                 save_wv->next = wv;
1027               else
1028                 first_wv->contents = wv;
1029               wv->name = pane_string;
1030               if (keymaps && !NILP (prefix))
1031                 wv->name++;
1032               wv->value = 0;
1033               wv->enabled = 1;
1034               wv->button_type = BUTTON_TYPE_NONE;
1035               wv->help = Qnil;
1036               save_wv = wv;
1037               prev_wv = 0;
1038             }
1039           else if (first_pane)
1040             {
1041               save_wv = wv;
1042               prev_wv = 0;
1043             }
1044           first_pane = 0;
1045           i += MENU_ITEMS_PANE_LENGTH;
1046         }
1047       else
1048         {
1049           /* Create a new item within current pane.  */
1050           Lisp_Object item_name, enable, descrip, def, type, selected, help;
1051           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
1052           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
1053           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
1054           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
1055           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
1056           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
1057           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
1059 #ifndef HAVE_MULTILINGUAL_MENU
1060           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
1061             {
1062               item_name = ENCODE_MENU_STRING (item_name);
1063               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
1064             }
1066           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
1067             {
1068               descrip = ENCODE_MENU_STRING (descrip);
1069               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
1070             }
1071 #endif /* not HAVE_MULTILINGUAL_MENU */
1073           wv = xmalloc_widget_value ();
1074           if (prev_wv)
1075             prev_wv->next = wv;
1076           else
1077             save_wv->contents = wv;
1078           wv->name = (char *) SDATA (item_name);
1079           if (!NILP (descrip))
1080             wv->key = (char *) SDATA (descrip);
1081           wv->value = 0;
1082           /* If this item has a null value,
1083              make the call_data null so that it won't display a box
1084              when the mouse is on it.  */
1085           wv->call_data
1086               = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
1087           wv->enabled = !NILP (enable);
1089           if (NILP (type))
1090             wv->button_type = BUTTON_TYPE_NONE;
1091           else if (EQ (type, QCtoggle))
1092             wv->button_type = BUTTON_TYPE_TOGGLE;
1093           else if (EQ (type, QCradio))
1094             wv->button_type = BUTTON_TYPE_RADIO;
1095           else
1096             abort ();
1098           wv->selected = !NILP (selected);
1100           if (! STRINGP (help))
1101             help = Qnil;
1103           wv->help = help;
1105           prev_wv = wv;
1107           i += MENU_ITEMS_ITEM_LENGTH;
1108         }
1109     }
1110   }
1111 #endif
1113   if (!NILP (title))
1114     {
1115       widget_value *wv_title = xmalloc_widget_value ();
1116       widget_value *wv_sep = xmalloc_widget_value ();
1118       /* Maybe replace this separator with a bitmap or owner-draw item
1119          so that it looks better.  Having two separators looks odd.  */
1120       wv_sep->name = "--";
1121       wv_sep->next = first_wv->contents;
1122       wv_sep->help = Qnil;
1124 #ifndef HAVE_MULTILINGUAL_MENU
1125       if (STRING_MULTIBYTE (title))
1126         title = ENCODE_MENU_STRING (title);
1127 #endif
1129       wv_title->name = (char *) SDATA (title);
1130       wv_title->enabled = NO;
1131       wv_title->button_type = BUTTON_TYPE_NONE;
1132       wv_title->help = Qnil;
1133       wv_title->next = wv_sep;
1134       first_wv->contents = wv_title;
1135     }
1137   pmenu = [[EmacsMenu alloc] initWithTitle:
1138                                [NSString stringWithUTF8String: SDATA (title)]];
1139   [pmenu fillWithWidgetValue: first_wv->contents];
1140   free_menubar_widget_value_tree (first_wv);
1141   unbind_to (specpdl_count2, Qnil);
1143   popup_activated_flag = 1;
1144   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1145   popup_activated_flag = 0;
1146   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1148   UNBLOCK_INPUT;
1149   discard_menu_items ();
1150   unbind_to (specpdl_count, Qnil);
1151   UNGCPRO;
1153   if (error_name) error (error_name);
1154   return tem;
1160 /* ==========================================================================
1162     Toolbar: externally-called functions
1164    ========================================================================== */
1166 void
1167 free_frame_tool_bar (FRAME_PTR f)
1168 /* --------------------------------------------------------------------------
1169     Under NS we just hide the toolbar until it might be needed again.
1170    -------------------------------------------------------------------------- */
1172   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1175 void
1176 update_frame_tool_bar (FRAME_PTR f)
1177 /* --------------------------------------------------------------------------
1178     Update toolbar contents
1179    -------------------------------------------------------------------------- */
1181   int i;
1182   EmacsToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar];
1184   [toolbar clearActive];
1186   /* update EmacsToolbar as in GtkUtils, build items list */
1187   for (i = 0; i < f->n_tool_bar_items; ++i)
1188     {
1189 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1190                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1192       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1193       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1194       int idx;
1195       int img_id;
1196       struct image *img;
1197       Lisp_Object image;
1198       Lisp_Object helpObj;
1199       char *helpText;
1201       /* If image is a vector, choose the image according to the
1202          button state.  */
1203       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1204       if (VECTORP (image))
1205         {
1206           /* NS toolbar auto-computes disabled and selected images */
1207           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1208           xassert (ASIZE (image) >= idx);
1209           image = AREF (image, idx);
1210         }
1211       else
1212         {
1213           idx = -1;
1214         }
1215       /* Ignore invalid image specifications.  */
1216       if (!valid_image_p (image))
1217         {
1218           NSLog (@"Invalid image for toolbar item");
1219           continue;
1220         }
1222       img_id = lookup_image (f, image);
1223       img = IMAGE_FROM_ID (f, img_id);
1224       prepare_image_for_display (f, img);
1226       if (img->load_failed_p || img->pixmap == nil)
1227         {
1228           NSLog (@"Could not prepare toolbar image for display.");
1229           continue;
1230         }
1232       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1233       if (NILP (helpObj))
1234         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1235       helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1237       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1238                                enabled: enabled_p];
1239 #undef TOOLPROP
1240     }
1242   if (![toolbar isVisible])
1243       [toolbar setVisible: YES];
1245   if ([toolbar changed])
1246     {
1247       /* inform app that toolbar has changed */
1248       NSDictionary *dict = [toolbar configurationDictionary];
1249       NSMutableDictionary *newDict = [dict mutableCopy];
1250       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1251       NSObject *key;
1252       while ((key = [keys nextObject]) != nil)
1253         {
1254           NSObject *val = [dict objectForKey: key];
1255           if ([val isKindOfClass: [NSArray class]])
1256             {
1257               [newDict setObject:
1258                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1259                           forKey: key];
1260               break;
1261             }
1262         }
1263       [toolbar setConfigurationFromDictionary: newDict];
1264       [newDict release];
1265     }
1270 /* ==========================================================================
1272     Toolbar: class implementation
1274    ========================================================================== */
1276 @implementation EmacsToolbar
1278 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1280   self = [super initWithIdentifier: identifier];
1281   emacsView = view;
1282   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1283   [self setSizeMode: NSToolbarSizeModeSmall];
1284   [self setDelegate: self];
1285   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1286   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1287   prevEnablement = enablement = 0L;
1288   return self;
1291 - (void)dealloc
1293   [prevIdentifiers release];
1294   [activeIdentifiers release];
1295   [identifierToItem release];
1296   [super dealloc];
1299 - (void) clearActive
1301   [prevIdentifiers release];
1302   prevIdentifiers = [activeIdentifiers copy];
1303   [activeIdentifiers removeAllObjects];
1304   prevEnablement = enablement;
1305   enablement = 0L;
1308 - (BOOL) changed
1310   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1311     enablement == prevEnablement ? NO : YES;
1314 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1315                         helpText: (char *)help enabled: (BOOL)enabled
1317   /* 1) come up w/identifier */
1318   NSString *identifier
1319       = [NSString stringWithFormat: @"%u", [img hash]];
1321   /* 2) create / reuse item */
1322   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1323   if (item == nil)
1324     {
1325       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1326                autorelease];
1327       [item setImage: img];
1328       [item setToolTip: [NSString stringWithCString: help]];
1329       [item setTarget: emacsView];
1330       [item setAction: @selector (toolbarClicked:)];
1331     }
1333   [item setTag: idx];
1334   [item setEnabled: enabled];
1336   /* 3) update state */
1337   [identifierToItem setObject: item forKey: identifier];
1338   [activeIdentifiers addObject: identifier];
1339   enablement = (enablement << 1) | (enabled == YES);
1342 /* This overrides super's implementation, which automatically sets
1343    all items to enabled state (for some reason). */
1344 - (void)validateVisibleItems { }
1347 /* delegate methods */
1349 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1350       itemForItemIdentifier: (NSString *)itemIdentifier
1351   willBeInsertedIntoToolbar: (BOOL)flag
1353   /* look up NSToolbarItem by identifier and return... */
1354   return [identifierToItem objectForKey: itemIdentifier];
1357 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1359   /* return entire set.. */
1360   return activeIdentifiers;
1363 /* for configuration palette (not yet supported) */
1364 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1366   /* return entire set... */
1367   return [identifierToItem allKeys];
1370 /* optional and unneeded */
1371 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1372 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1373 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1375 @end  /* EmacsToolbar */
1379 /* ==========================================================================
1381     Tooltip: class implementation
1383    ========================================================================== */
1385 /* Needed because NeXTstep does not provide enough control over tooltip
1386    display. */
1387 @implementation EmacsTooltip
1389 - init
1391   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1392                                             blue: 0.792 alpha: 0.95];
1393   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1394   NSFont *sfont = [font screenFont];
1395   int height = [sfont ascender] - [sfont descender];
1396 /*[font boundingRectForFont].size.height; */
1397   NSRect r = NSMakeRect (0, 0, 100, height+6);
1399   textField = [[NSTextField alloc] initWithFrame: r];
1400   [textField setFont: font];
1401   [textField setBackgroundColor: col];
1403   [textField setEditable: NO];
1404   [textField setSelectable: NO];
1405   [textField setBordered: YES];
1406   [textField setBezeled: YES];
1407   [textField setDrawsBackground: YES];
1409   win = [[NSWindow alloc]
1410             initWithContentRect: [textField frame]
1411                       styleMask: 0
1412                         backing: NSBackingStoreBuffered
1413                           defer: YES];
1414   [win setReleasedWhenClosed: NO];
1415   [win setDelegate: self];
1416   [[win contentView] addSubview: textField];
1417 /*  [win setBackgroundColor: col]; */
1418   [win setOpaque: NO];
1420   return self;
1423 - (void) dealloc
1425   [win close];
1426   [win release];
1427   [textField release];
1428   [super dealloc];
1431 - (void) setText: (char *)text
1433   NSString *str = [NSString stringWithUTF8String: text];
1434   NSRect r = [textField frame];
1435   r.size.width = [[[textField font] screenFont] widthOfString: str] + 8;
1436   [textField setFrame: r];
1437   [textField setStringValue: str];
1440 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1442   NSRect wr = [win frame];
1444   wr.origin = NSMakePoint (x, y);
1445   wr.size = [textField frame].size;
1447   [win setFrame: wr display: YES];
1448   [win orderFront: self];
1449   [win display];
1450   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1451                                          selector: @selector (hide)
1452                                          userInfo: nil repeats: NO];
1453   [timer retain];
1456 - (void) hide
1458   [win close];
1459   if (timer != nil)
1460     {
1461       if ([timer isValid])
1462         [timer invalidate];
1463       [timer release];
1464       timer = nil;
1465     }
1468 - (BOOL) isActive
1470   return timer != nil;
1473 - (NSRect) frame
1475   return [textField frame];
1478 @end  /* EmacsTooltip */
1482 /* ==========================================================================
1484     Popup Dialog: implementing functions
1486    ========================================================================== */
1488 Lisp_Object
1489 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1491   id dialog;
1492   Lisp_Object window, tem;
1493   struct frame *f;
1494   NSPoint p;
1495   BOOL isQ;
1497   NSTRACE (x-popup-dialog);
1498   
1499   check_ns ();
1501   isQ = NILP (header);
1503   if (EQ (position, Qt)
1504       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1505                                || EQ (XCAR (position), Qtool_bar))))
1506     {
1507       window = selected_window;
1508     }
1509   else if (CONSP (position))
1510     {
1511       Lisp_Object tem;
1512       tem = Fcar (position);
1513       if (XTYPE (tem) == Lisp_Cons)
1514         window = Fcar (Fcdr (position));
1515       else
1516         {
1517           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1518           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1519         }
1520     }
1521   else if (WINDOWP (position) || FRAMEP (position))
1522     {
1523       window = position;
1524     }
1525   else
1526     window = Qnil;
1528   if (FRAMEP (window))
1529     f = XFRAME (window);
1530   else if (WINDOWP (window))
1531     {
1532       CHECK_LIVE_WINDOW (window);
1533       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1534     }
1535   else
1536     CHECK_WINDOW (window);
1538   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1539   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1540   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1541                                            isQuestion: isQ];
1542   popup_activated_flag = 1;
1543   tem = [dialog runDialogAt: p];
1544   popup_activated_flag = 0;
1546   [dialog close];
1548   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1549   return tem;
1553 /* ==========================================================================
1555     Popup Dialog: class implementation
1557    ========================================================================== */
1559 @interface FlippedView : NSView
1562 @end
1564 @implementation FlippedView
1565 - (BOOL)isFlipped
1567   return YES;
1569 @end
1571 @implementation EmacsDialogPanel
1573 #define SPACER          8.0
1574 #define ICONSIZE        64.0
1575 #define TEXTHEIGHT      20.0
1576 #define MINCELLWIDTH    90.0
1578 - initWithContentRect: (NSRect)contentRect styleMask: (unsigned int)aStyle
1579               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1581   NSSize spacing = {SPACER, SPACER};
1582   NSRect area;
1583   char this_cmd_name[80];
1584   id cell;
1585   static NSImageView *imgView;
1586   static FlippedView *contentView;
1588   if (imgView == nil)
1589     {
1590       NSImage *img;
1591       area.origin.x   = 3*SPACER;
1592       area.origin.y   = 2*SPACER;
1593       area.size.width = ICONSIZE;
1594       area.size.height= ICONSIZE;
1595       img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1596       [img setScalesWhenResized: YES];
1597       [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1598       imgView = [[NSImageView alloc] initWithFrame: area];
1599       [imgView setImage: img];
1600       [imgView setEditable: NO];
1601       [img release];
1602     }
1604   aStyle = NSTitledWindowMask;
1605   flag = YES;
1606   rows = 0;
1607   cols = 1;
1608   [super initWithContentRect: contentRect styleMask: aStyle
1609                      backing: backingType defer: flag];
1610   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1611   [self setContentView: contentView];
1613   [[self contentView] setAutoresizesSubviews: YES];
1615   [[self contentView] addSubview: imgView];
1616   [self setTitle: @""];
1618   area.origin.x   += ICONSIZE+2*SPACER;
1619 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1620   area.size.width = 400;
1621   area.size.height= TEXTHEIGHT;
1622   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1623   [[self contentView] addSubview: command];
1624   [command setStringValue: @"Emacs"];
1625   [command setDrawsBackground: NO];
1626   [command setBezeled: NO];
1627   [command setSelectable: NO];
1628   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1630 /*  area.origin.x   = ICONSIZE+2*SPACER;
1631   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1632   area.size.width = 400;
1633   area.size.height= 2;
1634   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1635   [[self contentView] addSubview: tem];
1636   [tem setTitlePosition: NSNoTitle];
1637   [tem setAutoresizingMask: NSViewWidthSizable];*/
1639 /*  area.origin.x = ICONSIZE+2*SPACER; */
1640   area.origin.y += TEXTHEIGHT+SPACER;
1641   area.size.width = 400;
1642   area.size.height= TEXTHEIGHT;
1643   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1644   [[self contentView] addSubview: title];
1645   [title setDrawsBackground: NO];
1646   [title setBezeled: NO];
1647   [title setSelectable: NO];
1648   [title setFont: [NSFont systemFontOfSize: 11.0]];
1650   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1651   [cell setBordered: NO];
1652   [cell setEnabled: NO];
1653   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1654   [cell setBezelStyle: NSRoundedBezelStyle];
1656   matrix = [[NSMatrix alloc] initWithFrame: contentRect 
1657                                       mode: NSHighlightModeMatrix 
1658                                  prototype: cell 
1659                               numberOfRows: 0 
1660                            numberOfColumns: 1];
1661   [[self contentView] addSubview: matrix];
1662   [matrix release];
1663   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1664                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1665   [matrix setIntercellSpacing: spacing];
1667   [self setOneShot: YES];
1668   [self setReleasedWhenClosed: YES];
1669   [self setHidesOnDeactivate: YES];
1670   return self;
1674 - (BOOL)windowShouldClose: (id)sender
1676   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1677   return NO;
1681 void process_dialog (id window, Lisp_Object list)
1683   Lisp_Object item;
1684   int row = 0;
1686   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1687     {
1688       item = XCAR (list);
1689       if (XTYPE (item) == Lisp_String)
1690         {
1691           [window addString: XSTRING (item)->data row: row++];
1692         }
1693       else if (XTYPE (item) == Lisp_Cons)
1694         {
1695           [window addButton: XSTRING (XCAR (item))->data
1696                       value: XCDR (item) row: row++];
1697         }
1698       else if (NILP (item))
1699         {
1700           [window addSplit];
1701           row = 0;
1702         }
1703     }
1707 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1709   id cell;
1710        
1711   if (row >= rows)
1712     {
1713       [matrix addRow];
1714       rows++;
1715     }
1716   cell = [matrix cellAtRow: row column: cols-1];
1717   [cell setTarget: self];
1718   [cell setAction: @selector (clicked: )];
1719   [cell setTitle: [NSString stringWithUTF8String: str]];
1720   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1721   [cell setBordered: YES];
1722   [cell setEnabled: YES];
1724   return self;
1728 - addString: (char *)str row: (int)row
1730   id cell;
1731        
1732   if (row >= rows)
1733     {
1734       [matrix addRow];
1735       rows++;
1736     }
1737   cell = [matrix cellAtRow: row column: cols-1];
1738   [cell setTitle: [NSString stringWithUTF8String: str]];
1739   [cell setBordered: YES];
1740   [cell setEnabled: NO];
1742   return self;
1746 - addSplit
1748   [matrix addColumn];
1749   cols++;
1750   return self;
1754 - clicked: sender
1756   NSArray *sellist = nil;
1757   EMACS_INT seltag;
1759   sellist = [sender selectedCells];
1760   if ([sellist count]<1) 
1761     return self;
1763   seltag = [[sellist objectAtIndex: 0] tag];
1764   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1765     [NSApp stopModalWithCode: seltag];
1766   return self;
1770 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1772   Lisp_Object head;
1773   [super init];
1775   if (XTYPE (contents) == Lisp_Cons)
1776     {
1777       head = Fcar (contents);
1778       process_dialog (self, Fcdr (contents));
1779     }
1780   else
1781     head = contents;
1783   if (XTYPE (head) == Lisp_String)
1784       [title setStringValue:
1785                  [NSString stringWithUTF8String: XSTRING (head)->data]];
1786   else if (isQ == YES)
1787       [title setStringValue: @"Question"];
1788   else
1789       [title setStringValue: @"Information"];
1791   {
1792     int i;
1793     NSRect r, s, t;
1795     if (cols == 1 && rows > 1)  /* Never told where to split */
1796       {
1797         [matrix addColumn];
1798         for (i = 0; i<rows/2; i++)
1799           {
1800             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1801                       atRow: i column: 1];
1802             [matrix removeRow: (rows+1)/2];
1803           }
1804       }
1806     [matrix sizeToFit];
1807     {
1808       NSSize csize = [matrix cellSize];
1809       if (csize.width < MINCELLWIDTH)
1810         {
1811           csize.width = MINCELLWIDTH;
1812           [matrix setCellSize: csize];
1813           [matrix sizeToCells];
1814         }
1815     }
1817     [title sizeToFit];
1818     [command sizeToFit];
1820     t = [matrix frame];
1821     r = [title frame];
1822     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1823       {
1824         t.origin.x   = r.origin.x;
1825         t.size.width = r.size.width;
1826       }
1827     r = [command frame];
1828     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1829       {
1830         t.origin.x   = r.origin.x;
1831         t.size.width = r.size.width;
1832       }
1834     r = [self frame];
1835     s = [(NSView *)[self contentView] frame];
1836     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1837     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1838     [self setFrame: r display: NO];
1839   }
1841   return self;
1845 - (void)dealloc
1847   { [super dealloc]; return; };
1851 - (Lisp_Object)runDialogAt: (NSPoint)p
1853   NSEvent *e;
1854   NSModalSession session;
1855   int ret;
1857   [self center];  /*XXX p ignored? */
1858   [self orderFront: NSApp];
1860   session = [NSApp beginModalSessionForWindow: self];
1861   while ((ret = [NSApp runModalSession: session]) == NSRunContinuesResponse)
1862     {
1863     (e = [NSApp nextEventMatchingMask: NSAnyEventMask
1864                             untilDate: [NSDate distantFuture]
1865                                inMode: NSModalPanelRunLoopMode
1866                               dequeue: NO]);
1867 /*fprintf (stderr, "ret = %d\te = %p\n", ret, e);*/
1868     }
1869   [NSApp endModalSession: session];
1871   {                             // FIXME: BIG UGLY HACK!!!
1872       Lisp_Object tmp;
1873       *(EMACS_INT*)(&tmp) = ret;
1874       return tmp;
1875   }
1878 @end
1882 /* ==========================================================================
1884     Lisp definitions
1886    ========================================================================== */
1888 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1889        doc: /* Cause the NS menu to be re-calculated.  */)
1890      ()
1892   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1893   return Qnil;
1897 DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
1898        doc: /* Pop up a deck-of-cards menu and return user's selection.
1899 POSITION is a position specification.  This is either a mouse button event
1900 or a list ((XOFFSET YOFFSET) WINDOW)
1901 where XOFFSET and YOFFSET are positions in pixels from the top left
1902 corner of WINDOW.  (WINDOW may be a window or a frame object.)
1903 This controls the position of the top left of the menu as a whole.
1904 If POSITION is t, it means to use the current mouse position.
1906 MENU is a specifier for a menu.  For the simplest case, MENU is a keymap.
1907 The menu items come from key bindings that have a menu string as well as
1908 a definition; actually, the \"definition\" in such a key binding looks like
1909 \(STRING . REAL-DEFINITION).  To give the menu a title, put a string into
1910 the keymap as a top-level element.
1912 If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.
1913 Otherwise, REAL-DEFINITION should be a valid key binding definition.
1915 You can also use a list of keymaps as MENU.
1916   Then each keymap makes a separate pane.
1918 When MENU is a keymap or a list of keymaps, the return value is the
1919 list of events corresponding to the user's choice. Note that
1920 `x-popup-menu' does not actually execute the command bound to that
1921 sequence of events.
1923 Alternatively, you can specify a menu of multiple panes
1924   with a list of the form (TITLE PANE1 PANE2...),
1925 where each pane is a list of form (TITLE ITEM1 ITEM2...).
1926 Each ITEM is normally a cons cell (STRING . VALUE);
1927 but a string can appear as an item--that makes a nonselectable line
1928 in the menu.
1929 With this form of menu, the return value is VALUE from the chosen item.  */)
1930      (position, menu)
1931      Lisp_Object position, menu;
1933   return ns_popup_menu (position, menu);
1937 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1938        doc: /* Pop up a dialog box and return user's selection.
1939 POSITION specifies which frame to use.
1940 This is normally a mouse button event or a window or frame.
1941 If POSITION is t, it means to use the frame the mouse is on.
1942 The dialog box appears in the middle of the specified frame.
1944 CONTENTS specifies the alternatives to display in the dialog box.
1945 It is a list of the form (DIALOG ITEM1 ITEM2...).
1946 Each ITEM is a cons cell (STRING . VALUE).
1947 The return value is VALUE from the chosen item.
1949 An ITEM may also be just a string--that makes a nonselectable item.
1950 An ITEM may also be nil--that means to put all preceding items
1951 on the left of the dialog box and all following items on the right.
1952 \(By default, approximately half appear on each side.)
1954 If HEADER is non-nil, the frame title for the box is "Information",
1955 otherwise it is "Question".
1957 If the user gets rid of the dialog box without making a valid choice,
1958 for instance using the window manager, then this produces a quit and
1959 `x-popup-dialog' does not return.  */)
1960      (position, contents, header)
1961      Lisp_Object position, contents, header;
1963   return ns_popup_dialog (position, contents, header);
1966 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1967        doc: /* Return t if a menu or popup dialog is active.  */)
1968      ()
1970   return popup_activated () ? Qt : Qnil;
1973 /* ==========================================================================
1975     Lisp interface declaration
1977    ========================================================================== */
1979 void
1980 syms_of_nsmenu ()
1982   defsubr (&Sx_popup_menu);
1983   defsubr (&Sx_popup_dialog);
1984   defsubr (&Sns_reset_menu);
1985   defsubr (&Smenu_or_popup_active_p);
1986   staticpro (&menu_items);
1987   menu_items = Qnil;
1989   Qdebug_on_next_call = intern ("debug-on-next-call");
1990   staticpro (&Qdebug_on_next_call);
1993 // arch-tag: 75773656-52e5-4c44-a398-47bd87b32619