Merge from trunk.
[emacs.git] / src / nsmenu.m
blob2a2f952e751b3ca2bc989b41f167365784645f7c
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007-2011 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>
27 #include <setjmp.h>
29 #include "lisp.h"
30 #include "window.h"
31 #include "buffer.h"
32 #include "keymap.h"
33 #include "coding.h"
34 #include "commands.h"
35 #include "blockinput.h"
36 #include "nsterm.h"
37 #include "termhooks.h"
38 #include "keyboard.h"
39 #include "menu.h"
41 #define NSMENUPROFILE 0
43 #if NSMENUPROFILE
44 #include <sys/timeb.h>
45 #include <sys/types.h>
46 #endif
48 #define MenuStagger 10.0
50 #if 0
51 int menu_trace_num = 0;
52 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
53                                 __FILE__, __LINE__, ++menu_trace_num)
54 #else
55 #define NSTRACE(x)
56 #endif
58 #if 0
59 /* Include lisp -> C common menu parsing code */
60 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
61 #include "nsmenu_common.c"
62 #endif
64 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
65 extern Lisp_Object QCtoggle, QCradio;
67 Lisp_Object Qdebug_on_next_call;
68 extern Lisp_Object Qoverriding_local_map, Qoverriding_terminal_local_map;
70 extern long context_menu_value;
71 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
73 /* Nonzero means a menu is currently active.  */
74 static int popup_activated_flag;
75 static NSModalSession popupSession;
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 (void)
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: ns_app_name];
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 #ifdef NS_IMPL_GNUSTEP
174   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
175 #endif
177   if (deep_p)
178     {
179       /* Fully parse one or more of the submenus. */
180       int n = 0;
181       int *submenu_start, *submenu_end;
182       int *submenu_top_level_items, *submenu_n_panes;
183       struct buffer *prev = current_buffer;
184       Lisp_Object buffer;
185       int specpdl_count = SPECPDL_INDEX ();
186       int previous_menu_items_used = f->menu_bar_items_used;
187       Lisp_Object *previous_items
188         = (Lisp_Object *) alloca (previous_menu_items_used
189                                   * sizeof (Lisp_Object));
191       /* lisp preliminaries */
192       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
193       specbind (Qinhibit_quit, Qt);
194       specbind (Qdebug_on_next_call, Qnil);
195       record_unwind_save_match_data ();
196       if (NILP (Voverriding_local_map_menu_flag))
197         {
198           specbind (Qoverriding_terminal_local_map, Qnil);
199           specbind (Qoverriding_local_map, Qnil);
200         }
201       set_buffer_internal_1 (XBUFFER (buffer));
203       /* TODO: for some reason this is not needed in other terms,
204            but some menu updates call Info-extract-pointer which causes
205            abort-on-error if waiting-for-input.  Needs further investigation. */
206       owfi = waiting_for_input;
207       waiting_for_input = 0;
209       /* lucid hook and possible reset */
210       safe_run_hooks (Qactivate_menubar_hook);
211       if (! NILP (Vlucid_menu_bar_dirty_flag))
212         call0 (Qrecompute_lucid_menubar);
213       safe_run_hooks (Qmenu_bar_update_hook);
214       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
216       /* Now ready to go */
217       items = FRAME_MENU_BAR_ITEMS (f);
219       /* Save the frame's previous menu bar contents data */
220       if (previous_menu_items_used)
221         memcpy (previous_items, &AREF (f->menu_bar_vector, 0),
222                 previous_menu_items_used * sizeof (Lisp_Object));
224       /* parse stage 1: extract from lisp */
225       save_menu_items ();
227       menu_items = f->menu_bar_vector;
228       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
229       submenu_start = (int *) alloca (ASIZE (items) * sizeof (int *));
230       submenu_end = (int *) alloca (ASIZE (items) * sizeof (int *));
231       submenu_n_panes = (int *) alloca (ASIZE (items) * sizeof (int));
232       submenu_top_level_items
233         = (int *) alloca (ASIZE (items) * sizeof (int *));
234       init_menu_items ();
235       for (i = 0; i < ASIZE (items); i += 4)
236         {
237           Lisp_Object key, string, maps;
239           key = AREF (items, i);
240           string = AREF (items, i + 1);
241           maps = AREF (items, i + 2);
242           if (NILP (string))
243             break;
245           /* FIXME: we'd like to only parse the needed submenu, but this
246                was causing crashes in the _common parsing code.. need to make
247                sure proper initialization done.. */
248 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
249              continue; */
251           submenu_start[i] = menu_items_used;
253           menu_items_n_panes = 0;
254           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
255           submenu_n_panes[i] = menu_items_n_panes;
256           submenu_end[i] = menu_items_used;
257           n++;
258         }
260       finish_menu_items ();
261       waiting_for_input = owfi;
264       if (submenu && n == 0)
265         {
266           /* should have found a menu for this one but didn't */
267           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
268                   submenuTitle);
269           discard_menu_items ();
270           unbind_to (specpdl_count, Qnil);
271           [pool release];
272           UNBLOCK_INPUT;
273           return;
274         }
276       /* parse stage 2: insert into lucid 'widget_value' structures
277          [comments in other terms say not to evaluate lisp code here] */
278       wv = xmalloc_widget_value ();
279       wv->name = "menubar";
280       wv->value = 0;
281       wv->enabled = 1;
282       wv->button_type = BUTTON_TYPE_NONE;
283       wv->help = Qnil;
284       first_wv = wv;
286       for (i = 0; i < 4*n; i += 4)
287         {
288           menu_items_n_panes = submenu_n_panes[i];
289           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
290                                       submenu_top_level_items[i]);
291           if (prev_wv)
292             prev_wv->next = wv;
293           else
294             first_wv->contents = wv;
295           /* Don't set wv->name here; GC during the loop might relocate it.  */
296           wv->enabled = 1;
297           wv->button_type = BUTTON_TYPE_NONE;
298           prev_wv = wv;
299         }
301       set_buffer_internal_1 (prev);
303       /* Compare the new menu items with previous, and leave off if no change */
304       /* FIXME: following other terms here, but seems like this should be
305            done before parse stage 2 above, since its results aren't used */
306       if (previous_menu_items_used
307           && (!submenu || (submenu && submenu == last_submenu))
308           && menu_items_used == previous_menu_items_used)
309         {
310           for (i = 0; i < previous_menu_items_used; i++)
311             /* FIXME: this ALWAYS fails on Buffers menu items.. something
312                  about their strings causes them to change every time, so we
313                  double-check failures */
314             if (!EQ (previous_items[i], AREF (menu_items, i)))
315               if (!(STRINGP (previous_items[i])
316                     && STRINGP (AREF (menu_items, i))
317                     && !strcmp (SDATA (previous_items[i]),
318                                 SDATA (AREF (menu_items, i)))))
319                   break;
320           if (i == previous_menu_items_used)
321             {
322               /* No change.. */
324 #if NSMENUPROFILE
325               ftime (&tb);
326               t += 1000*tb.time+tb.millitm;
327               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
328 #endif
330               free_menubar_widget_value_tree (first_wv);
331               discard_menu_items ();
332               unbind_to (specpdl_count, Qnil);
333               [pool release];
334               UNBLOCK_INPUT;
335               return;
336             }
337         }
338       /* The menu items are different, so store them in the frame */
339       /* FIXME: this is not correct for single-submenu case */
340       f->menu_bar_vector = menu_items;
341       f->menu_bar_items_used = menu_items_used;
343       /* Calls restore_menu_items, etc., as they were outside */
344       unbind_to (specpdl_count, Qnil);
346       /* Parse stage 2a: now GC cannot happen during the lifetime of the
347          widget_value, so it's safe to store data from a Lisp_String */
348       wv = first_wv->contents;
349       for (i = 0; i < ASIZE (items); i += 4)
350         {
351           Lisp_Object string;
352           string = AREF (items, i + 1);
353           if (NILP (string))
354             break;
355 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
356                continue; */
358           wv->name = SSDATA (string);
359           update_submenu_strings (wv->contents);
360           wv = wv->next;
361         }
363       /* Now, update the NS menu; if we have a submenu, use that, otherwise
364          create a new menu for each sub and fill it. */
365       if (submenu)
366         {
367           for (wv = first_wv->contents; wv; wv = wv->next)
368             {
369               if (!strcmp (submenuTitle, wv->name))
370                 {
371                   [submenu fillWithWidgetValue: wv->contents];
372                   last_submenu = submenu;
373                   break;
374                 }
375             }
376         }
377       else
378         {
379           [menu fillWithWidgetValue: first_wv->contents];
380         }
382     }
383   else
384     {
385       static int n_previous_strings = 0;
386       static char previous_strings[100][10];
387       static struct frame *last_f = NULL;
388       int n;
389       Lisp_Object string;
391       wv = xmalloc_widget_value ();
392       wv->name = "menubar";
393       wv->value = 0;
394       wv->enabled = 1;
395       wv->button_type = BUTTON_TYPE_NONE;
396       wv->help = Qnil;
397       first_wv = wv;
399       /* Make widget-value tree w/ just the top level menu bar strings */
400       items = FRAME_MENU_BAR_ITEMS (f);
401       if (NILP (items))
402         {
403           [pool release];
404           UNBLOCK_INPUT;
405           return;
406         }
409       /* check if no change.. this mechanism is a bit rough, but ready */
410       n = ASIZE (items) / 4;
411       if (f == last_f && n_previous_strings == n)
412         {
413           for (i = 0; i<n; i++)
414             {
415               string = AREF (items, 4*i+1);
417               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
418                 continue;
419               if (NILP (string))
420                 if (previous_strings[i][0])
421                   break;
422               else
423                 continue;
424               if (strncmp (previous_strings[i], SDATA (string), 10))
425                 break;
426             }
428           if (i == n)
429             {
430               [pool release];
431               UNBLOCK_INPUT;
432               return;
433             }
434         }
436       [menu clear];
437       for (i = 0; i < ASIZE (items); i += 4)
438         {
439           string = AREF (items, i + 1);
440           if (NILP (string))
441             break;
443           if (n < 100)
444             strncpy (previous_strings[i/4], SDATA (string), 10);
446           wv = xmalloc_widget_value ();
447           wv->name = SSDATA (string);
448           wv->value = 0;
449           wv->enabled = 1;
450           wv->button_type = BUTTON_TYPE_NONE;
451           wv->help = Qnil;
452           wv->call_data = (void *) (EMACS_INT) (-1);
454 #ifdef NS_IMPL_COCOA
455           /* we'll update the real copy under app menu when time comes */
456           if (!strcmp ("Services", wv->name))
457             {
458               /* but we need to make sure it will update on demand */
459               [svcsMenu setFrame: f];
460               [svcsMenu setDelegate: svcsMenu];
461             }
462           else
463 #endif
464           [menu addSubmenuWithTitle: wv->name forFrame: f];
466           if (prev_wv)
467             prev_wv->next = wv;
468           else
469             first_wv->contents = wv;
470           prev_wv = wv;
471         }
473       last_f = f;
474       if (n < 100)
475         n_previous_strings = n;
476       else
477         n_previous_strings = 0;
479     }
480   free_menubar_widget_value_tree (first_wv);
483 #if NSMENUPROFILE
484   ftime (&tb);
485   t += 1000*tb.time+tb.millitm;
486   fprintf (stderr, "Menu update took %ld msec.\n", t);
487 #endif
489   /* set main menu */
490   if (needsSet)
491     [NSApp setMainMenu: menu];
493   [pool release];
494   UNBLOCK_INPUT;
499 /* Main emacs core entry point for menubar menus: called to indicate that the
500    frame's menus have changed, and the *step representation should be updated
501    from Lisp. */
502 void
503 set_frame_menubar (struct frame *f, int first_time, int deep_p)
505   ns_update_menubar (f, deep_p, nil);
509 /* ==========================================================================
511     Menu: class implementation
513    ========================================================================== */
516 /* Menu that can define itself from Emacs "widget_value"s and will lazily
517    update itself when user clicked.  Based on Carbon/AppKit implementation
518    by Yamamoto Mitsuharu. */
519 @implementation EmacsMenu
521 /* override designated initializer */
522 - initWithTitle: (NSString *)title
524   if (self = [super initWithTitle: title])
525     [self setAutoenablesItems: NO];
526   return self;
530 /* used for top-level */
531 - initWithTitle: (NSString *)title frame: (struct frame *)f
533   [self initWithTitle: title];
534   frame = f;
535 #ifdef NS_IMPL_COCOA
536   [self setDelegate: self];
537 #endif
538   return self;
542 - (void)setFrame: (struct frame *)f
544   frame = f;
548 /* delegate method called when a submenu is being opened: run a 'deep' call
549    to set_frame_menubar */
550 - (void)menuNeedsUpdate: (NSMenu *)menu
552   NSEvent *event;
553   if (!FRAME_LIVE_P (frame))
554     return;
555   event = [[FRAME_NS_VIEW (frame) window] currentEvent];
556   /* HACK: Cocoa/Carbon will request update on every keystroke
557      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
558      since key equivalents are handled through emacs.
559      On Leopard, even keystroke events generate SystemDefined events, but
560      their subtype is 8. */
561   if ([event type] != NSSystemDefined || [event subtype] == 8
562       /* Also, don't try this if from an event picked up asynchronously,
563          as lots of lisp evaluation happens in ns_update_menubar. */
564       || handling_signal != 0)
565     return;
566 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
567   ns_update_menubar (frame, 1, self);
571 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
573   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
574       && FRAME_NS_VIEW (SELECTED_FRAME ()))
575     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
576   return YES;
580 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
581    into an accelerator string.  We are only able to display a single character
582    for an accelerator, together with an optional modifier combination.  (Under
583    Carbon more control was possible, but in Cocoa multi-char strings passed to
584    NSMenuItem get ignored.  For now we try to display a super-single letter
585    combo, and return the others as strings to be appended to the item title.
586    (This is signaled by setting keyEquivModMask to 0 for now.) */
587 -(NSString *)parseKeyEquiv: (const char *)key
589   const char *tpos = key;
590   keyEquivModMask = NSCommandKeyMask;
592   if (!key || !strlen (key))
593     return @"";
595   while (*tpos == ' ' || *tpos == '(')
596     tpos++;
597   if ((*tpos == 's') && (*(tpos+1) == '-'))
598     {
599       return [NSString stringWithFormat: @"%c", tpos[2]];
600     }
601   keyEquivModMask = 0; /* signal */
602   return [NSString stringWithUTF8String: tpos];
606 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
608   NSMenuItem *item;
609   widget_value *wv = (widget_value *)wvptr;
611   if (menu_separator_name_p (wv->name))
612     {
613       item = [NSMenuItem separatorItem];
614       [self addItem: item];
615     }
616   else
617     {
618       NSString *title, *keyEq;
619       title = [NSString stringWithUTF8String: wv->name];
620       if (title == nil)
621         title = @"< ? >";  /* (get out in the open so we know about it) */
623       keyEq = [self parseKeyEquiv: wv->key];
624 #ifdef NS_IMPL_COCOA
625       /* OS X just ignores modifier strings longer than one character */
626       if (keyEquivModMask == 0)
627         title = [title stringByAppendingFormat: @" (%@)", keyEq];
628 #endif
630       item = [self addItemWithTitle: (NSString *)title
631                              action: @selector (menuDown:)
632                       keyEquivalent: keyEq];
633       [item setKeyEquivalentModifierMask: keyEquivModMask];
635       [item setEnabled: wv->enabled];
637       /* Draw radio buttons and tickboxes */
638       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
639                            wv->button_type == BUTTON_TYPE_RADIO))
640         [item setState: NSOnState];
641       else
642         [item setState: NSOffState];
644       [item setTag: (NSInteger)wv->call_data];
645     }
647   return item;
651 /* convenience */
652 -(void)clear
654   int n;
656   for (n = [self numberOfItems]-1; n >= 0; n--)
657     {
658       NSMenuItem *item = [self itemAtIndex: n];
659       NSString *title = [item title];
660       if (([title length] == 0  /* OSX 10.5 */
661            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
662            || [@"Apple" isEqualToString: title]) /* older */
663           && ![item isSeparatorItem])
664         continue;
665       [self removeItemAtIndex: n];
666     }
670 - (void)fillWithWidgetValue: (void *)wvptr
672   widget_value *wv = (widget_value *)wvptr;
674   /* clear existing contents */
675   [self setMenuChangedMessagesEnabled: NO];
676   [self clear];
678   /* add new contents */
679   for (; wv != NULL; wv = wv->next)
680     {
681       NSMenuItem *item = [self addItemWithWidgetValue: wv];
683       if (wv->contents)
684         {
685           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
687           [self setSubmenu: submenu forItem: item];
688           [submenu fillWithWidgetValue: wv->contents];
689           [submenu release];
690           [item setAction: nil];
691         }
692     }
694   [self setMenuChangedMessagesEnabled: YES];
695 #ifdef NS_IMPL_GNUSTEP
696   if ([[self window] isVisible])
697     [self sizeToFit];
698 #else
699   if ([self supermenu] == nil)
700     [self sizeToFit];
701 #endif
705 /* adds an empty submenu and returns it */
706 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
708   NSString *titleStr = [NSString stringWithUTF8String: title];
709   NSMenuItem *item = [self addItemWithTitle: titleStr
710                                      action: nil /*@selector (menuDown:) */
711                               keyEquivalent: @""];
712   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
713   [self setSubmenu: submenu forItem: item];
714   [submenu release];
715   return submenu;
718 /* run a menu in popup mode */
719 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
720                  keymaps: (int)keymaps
722   EmacsView *view = FRAME_NS_VIEW (f);
723   NSEvent *e, *event;
724   long retVal;
726 /*   p = [view convertPoint:p fromView: nil]; */
727   p.y = NSHeight ([view frame]) - p.y;
728   e = [[view window] currentEvent];
729    event = [NSEvent mouseEventWithType: NSRightMouseDown
730                               location: p
731                          modifierFlags: 0
732                              timestamp: [e timestamp]
733                           windowNumber: [[view window] windowNumber]
734                                context: [e context]
735                            eventNumber: 0/*[e eventNumber] */
736                             clickCount: 1
737                               pressure: 0];
739   context_menu_value = -1;
740   [NSMenu popUpContextMenu: self withEvent: event forView: view];
741   retVal = context_menu_value;
742   context_menu_value = 0;
743   return retVal > 0
744       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
745       : Qnil;
748 @end  /* EmacsMenu */
752 /* ==========================================================================
754     Context Menu: implementing functions
756    ========================================================================== */
758 Lisp_Object
759 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
760               Lisp_Object title, const char **error)
762   EmacsMenu *pmenu;
763   NSPoint p;
764   Lisp_Object window, tem, keymap;
765   int specpdl_count = SPECPDL_INDEX ();
766   widget_value *wv, *first_wv = 0;
768   p.x = x; p.y = y;
770   /* now parse stage 2 as in ns_update_menubar */
771   wv = xmalloc_widget_value ();
772   wv->name = "contextmenu";
773   wv->value = 0;
774   wv->enabled = 1;
775   wv->button_type = BUTTON_TYPE_NONE;
776   wv->help = Qnil;
777   first_wv = wv;
779 #if 0
780   /* FIXME: a couple of one-line differences prevent reuse */
781   wv = digest_single_submenu (0, menu_items_used, Qnil);
782 #else
783   {
784   widget_value *save_wv = 0, *prev_wv = 0;
785   widget_value **submenu_stack
786     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
787 /*   Lisp_Object *subprefix_stack
788        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
789   int submenu_depth = 0;
790   int first_pane = 1;
791   int i;
793   /* Loop over all panes and items, filling in the tree.  */
794   i = 0;
795   while (i < menu_items_used)
796     {
797       if (EQ (AREF (menu_items, i), Qnil))
798         {
799           submenu_stack[submenu_depth++] = save_wv;
800           save_wv = prev_wv;
801           prev_wv = 0;
802           first_pane = 1;
803           i++;
804         }
805       else if (EQ (AREF (menu_items, i), Qlambda))
806         {
807           prev_wv = save_wv;
808           save_wv = submenu_stack[--submenu_depth];
809           first_pane = 0;
810           i++;
811         }
812       else if (EQ (AREF (menu_items, i), Qt)
813                && submenu_depth != 0)
814         i += MENU_ITEMS_PANE_LENGTH;
815       /* Ignore a nil in the item list.
816          It's meaningful only for dialog boxes.  */
817       else if (EQ (AREF (menu_items, i), Qquote))
818         i += 1;
819       else if (EQ (AREF (menu_items, i), Qt))
820         {
821           /* Create a new pane.  */
822           Lisp_Object pane_name, prefix;
823           const char *pane_string;
825           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
826           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
828 #ifndef HAVE_MULTILINGUAL_MENU
829           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
830             {
831               pane_name = ENCODE_MENU_STRING (pane_name);
832               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
833             }
834 #endif
835           pane_string = (NILP (pane_name)
836                          ? "" : SSDATA (pane_name));
837           /* If there is just one top-level pane, put all its items directly
838              under the top-level menu.  */
839           if (menu_items_n_panes == 1)
840             pane_string = "";
842           /* If the pane has a meaningful name,
843              make the pane a top-level menu item
844              with its items as a submenu beneath it.  */
845           if (!keymaps && strcmp (pane_string, ""))
846             {
847               wv = xmalloc_widget_value ();
848               if (save_wv)
849                 save_wv->next = wv;
850               else
851                 first_wv->contents = wv;
852               wv->name = pane_string;
853               if (keymaps && !NILP (prefix))
854                 wv->name++;
855               wv->value = 0;
856               wv->enabled = 1;
857               wv->button_type = BUTTON_TYPE_NONE;
858               wv->help = Qnil;
859               save_wv = wv;
860               prev_wv = 0;
861             }
862           else if (first_pane)
863             {
864               save_wv = wv;
865               prev_wv = 0;
866             }
867           first_pane = 0;
868           i += MENU_ITEMS_PANE_LENGTH;
869         }
870       else
871         {
872           /* Create a new item within current pane.  */
873           Lisp_Object item_name, enable, descrip, def, type, selected, help;
874           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
875           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
876           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
877           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
878           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
879           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
880           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
882 #ifndef HAVE_MULTILINGUAL_MENU
883           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
884             {
885               item_name = ENCODE_MENU_STRING (item_name);
886               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
887             }
889           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
890             {
891               descrip = ENCODE_MENU_STRING (descrip);
892               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
893             }
894 #endif /* not HAVE_MULTILINGUAL_MENU */
896           wv = xmalloc_widget_value ();
897           if (prev_wv)
898             prev_wv->next = wv;
899           else
900             save_wv->contents = wv;
901           wv->name = SSDATA (item_name);
902           if (!NILP (descrip))
903             wv->key = SSDATA (descrip);
904           wv->value = 0;
905           /* If this item has a null value,
906              make the call_data null so that it won't display a box
907              when the mouse is on it.  */
908           wv->call_data
909               = !NILP (def) ? (void *) &AREF (menu_items, i) : 0;
910           wv->enabled = !NILP (enable);
912           if (NILP (type))
913             wv->button_type = BUTTON_TYPE_NONE;
914           else if (EQ (type, QCtoggle))
915             wv->button_type = BUTTON_TYPE_TOGGLE;
916           else if (EQ (type, QCradio))
917             wv->button_type = BUTTON_TYPE_RADIO;
918           else
919             abort ();
921           wv->selected = !NILP (selected);
923           if (! STRINGP (help))
924             help = Qnil;
926           wv->help = help;
928           prev_wv = wv;
930           i += MENU_ITEMS_ITEM_LENGTH;
931         }
932     }
933   }
934 #endif
936   if (!NILP (title))
937     {
938       widget_value *wv_title = xmalloc_widget_value ();
939       widget_value *wv_sep = xmalloc_widget_value ();
941       /* Maybe replace this separator with a bitmap or owner-draw item
942          so that it looks better.  Having two separators looks odd.  */
943       wv_sep->name = "--";
944       wv_sep->next = first_wv->contents;
945       wv_sep->help = Qnil;
947 #ifndef HAVE_MULTILINGUAL_MENU
948       if (STRING_MULTIBYTE (title))
949         title = ENCODE_MENU_STRING (title);
950 #endif
952       wv_title->name = SSDATA (title);
953       wv_title->enabled = NO;
954       wv_title->button_type = BUTTON_TYPE_NONE;
955       wv_title->help = Qnil;
956       wv_title->next = wv_sep;
957       first_wv->contents = wv_title;
958     }
960   pmenu = [[EmacsMenu alloc] initWithTitle:
961                                [NSString stringWithUTF8String: SDATA (title)]];
962   [pmenu fillWithWidgetValue: first_wv->contents];
963   free_menubar_widget_value_tree (first_wv);
964   unbind_to (specpdl_count, Qnil);
966   popup_activated_flag = 1;
967   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
968   popup_activated_flag = 0;
969   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
971   return tem;
975 /* ==========================================================================
977     Toolbar: externally-called functions
979    ========================================================================== */
981 void
982 free_frame_tool_bar (FRAME_PTR f)
983 /* --------------------------------------------------------------------------
984     Under NS we just hide the toolbar until it might be needed again.
985    -------------------------------------------------------------------------- */
987   BLOCK_INPUT;
988   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
989   FRAME_TOOLBAR_HEIGHT (f) = 0;
990   UNBLOCK_INPUT;
993 void
994 update_frame_tool_bar (FRAME_PTR f)
995 /* --------------------------------------------------------------------------
996     Update toolbar contents
997    -------------------------------------------------------------------------- */
999   int i;
1000   EmacsView *view = FRAME_NS_VIEW (f);
1001   NSWindow *window = [view window];
1002   EmacsToolbar *toolbar = [view toolbar];
1004   BLOCK_INPUT;
1005   [toolbar clearActive];
1007   /* update EmacsToolbar as in GtkUtils, build items list */
1008   for (i = 0; i < f->n_tool_bar_items; ++i)
1009     {
1010 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1011                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1013       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1014       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1015       int idx;
1016       int img_id;
1017       struct image *img;
1018       Lisp_Object image;
1019       Lisp_Object helpObj;
1020       const char *helpText;
1022       /* If image is a vector, choose the image according to the
1023          button state.  */
1024       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1025       if (VECTORP (image))
1026         {
1027           /* NS toolbar auto-computes disabled and selected images */
1028           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1029           xassert (ASIZE (image) >= idx);
1030           image = AREF (image, idx);
1031         }
1032       else
1033         {
1034           idx = -1;
1035         }
1036       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1037       if (NILP (helpObj))
1038         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1039       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1041       /* Ignore invalid image specifications.  */
1042       if (!valid_image_p (image))
1043         {
1044           /* Don't log anything, GNUS makes invalid images all the time.  */
1045           continue;
1046         }
1048       img_id = lookup_image (f, image);
1049       img = IMAGE_FROM_ID (f, img_id);
1050       prepare_image_for_display (f, img);
1052       if (img->load_failed_p || img->pixmap == nil)
1053         {
1054           NSLog (@"Could not prepare toolbar image for display.");
1055           continue;
1056         }
1058       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1059                                enabled: enabled_p];
1060 #undef TOOLPROP
1061     }
1063   if (![toolbar isVisible])
1064       [toolbar setVisible: YES];
1066   if ([toolbar changed])
1067     {
1068       /* inform app that toolbar has changed */
1069       NSDictionary *dict = [toolbar configurationDictionary];
1070       NSMutableDictionary *newDict = [dict mutableCopy];
1071       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1072       NSObject *key;
1073       while ((key = [keys nextObject]) != nil)
1074         {
1075           NSObject *val = [dict objectForKey: key];
1076           if ([val isKindOfClass: [NSArray class]])
1077             {
1078               [newDict setObject:
1079                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1080                           forKey: key];
1081               break;
1082             }
1083         }
1084       [toolbar setConfigurationFromDictionary: newDict];
1085       [newDict release];
1086     }
1088   FRAME_TOOLBAR_HEIGHT (f) =
1089     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1090     - FRAME_NS_TITLEBAR_HEIGHT (f);
1091   UNBLOCK_INPUT;
1095 /* ==========================================================================
1097     Toolbar: class implementation
1099    ========================================================================== */
1101 @implementation EmacsToolbar
1103 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1105   self = [super initWithIdentifier: identifier];
1106   emacsView = view;
1107   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1108   [self setSizeMode: NSToolbarSizeModeSmall];
1109   [self setDelegate: self];
1110   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1111   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1112   prevEnablement = enablement = 0L;
1113   return self;
1116 - (void)dealloc
1118   [prevIdentifiers release];
1119   [activeIdentifiers release];
1120   [identifierToItem release];
1121   [super dealloc];
1124 - (void) clearActive
1126   [prevIdentifiers release];
1127   prevIdentifiers = [activeIdentifiers copy];
1128   [activeIdentifiers removeAllObjects];
1129   prevEnablement = enablement;
1130   enablement = 0L;
1133 - (BOOL) changed
1135   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1136     enablement == prevEnablement ? NO : YES;
1139 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1140                         helpText: (const char *)help enabled: (BOOL)enabled
1142   /* 1) come up w/identifier */
1143   NSString *identifier
1144       = [NSString stringWithFormat: @"%u", [img hash]];
1146   /* 2) create / reuse item */
1147   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1148   if (item == nil)
1149     {
1150       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1151                autorelease];
1152       [item setImage: img];
1153       [item setToolTip: [NSString stringWithUTF8String: help]];
1154       [item setTarget: emacsView];
1155       [item setAction: @selector (toolbarClicked:)];
1156     }
1158   [item setTag: idx];
1159   [item setEnabled: enabled];
1161   /* 3) update state */
1162   [identifierToItem setObject: item forKey: identifier];
1163   [activeIdentifiers addObject: identifier];
1164   enablement = (enablement << 1) | (enabled == YES);
1167 /* This overrides super's implementation, which automatically sets
1168    all items to enabled state (for some reason). */
1169 - (void)validateVisibleItems { }
1172 /* delegate methods */
1174 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1175       itemForItemIdentifier: (NSString *)itemIdentifier
1176   willBeInsertedIntoToolbar: (BOOL)flag
1178   /* look up NSToolbarItem by identifier and return... */
1179   return [identifierToItem objectForKey: itemIdentifier];
1182 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1184   /* return entire set.. */
1185   return activeIdentifiers;
1188 /* for configuration palette (not yet supported) */
1189 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1191   /* return entire set... */
1192   return [identifierToItem allKeys];
1195 /* optional and unneeded */
1196 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1197 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1198 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1200 @end  /* EmacsToolbar */
1204 /* ==========================================================================
1206     Tooltip: class implementation
1208    ========================================================================== */
1210 /* Needed because NeXTstep does not provide enough control over tooltip
1211    display. */
1212 @implementation EmacsTooltip
1214 - init
1216   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1217                                             blue: 0.792 alpha: 0.95];
1218   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1219   NSFont *sfont = [font screenFont];
1220   int height = [sfont ascender] - [sfont descender];
1221 /*[font boundingRectForFont].size.height; */
1222   NSRect r = NSMakeRect (0, 0, 100, height+6);
1224   textField = [[NSTextField alloc] initWithFrame: r];
1225   [textField setFont: font];
1226   [textField setBackgroundColor: col];
1228   [textField setEditable: NO];
1229   [textField setSelectable: NO];
1230   [textField setBordered: YES];
1231   [textField setBezeled: YES];
1232   [textField setDrawsBackground: YES];
1234   win = [[NSWindow alloc]
1235             initWithContentRect: [textField frame]
1236                       styleMask: 0
1237                         backing: NSBackingStoreBuffered
1238                           defer: YES];
1239   [win setReleasedWhenClosed: NO];
1240   [win setDelegate: self];
1241   [[win contentView] addSubview: textField];
1242 /*  [win setBackgroundColor: col]; */
1243   [win setOpaque: NO];
1245   return self;
1248 - (void) dealloc
1250   [win close];
1251   [win release];
1252   [textField release];
1253   [super dealloc];
1256 - (void) setText: (char *)text
1258   NSString *str = [NSString stringWithUTF8String: text];
1259   NSRect r = [textField frame];
1260   NSSize textSize = [str sizeWithAttributes:
1261      [NSDictionary dictionaryWithObject: [[textField font] screenFont]
1262                                  forKey: NSFontAttributeName]];
1263   NSSize padSize = [[[textField font] screenFont]
1264                      boundingRectForFont].size;
1266   r.size.width = textSize.width + padSize.width/2;
1267   r.size.height = textSize.height + padSize.height/2;
1268   [textField setFrame: r];
1269   [textField setStringValue: str];
1272 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1274   NSRect wr = [win frame];
1276   wr.origin = NSMakePoint (x, y);
1277   wr.size = [textField frame].size;
1279   [win setFrame: wr display: YES];
1280   [win orderFront: self];
1281   [win display];
1282   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1283                                          selector: @selector (hide)
1284                                          userInfo: nil repeats: NO];
1285   [timer retain];
1288 - (void) hide
1290   [win close];
1291   if (timer != nil)
1292     {
1293       if ([timer isValid])
1294         [timer invalidate];
1295       [timer release];
1296       timer = nil;
1297     }
1300 - (BOOL) isActive
1302   return timer != nil;
1305 - (NSRect) frame
1307   return [textField frame];
1310 @end  /* EmacsTooltip */
1314 /* ==========================================================================
1316     Popup Dialog: implementing functions
1318    ========================================================================== */
1321 static Lisp_Object
1322 pop_down_menu (Lisp_Object arg)
1324   struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1325   if (popup_activated_flag)
1326     {
1327       popup_activated_flag = 0;
1328       BLOCK_INPUT;
1329       [NSApp endModalSession: popupSession];
1330       [((EmacsDialogPanel *) (p->pointer)) close];
1331       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1332       UNBLOCK_INPUT;
1333     }
1334   return Qnil;
1338 Lisp_Object
1339 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1341   id dialog;
1342   Lisp_Object window, tem;
1343   struct frame *f;
1344   NSPoint p;
1345   BOOL isQ;
1347   NSTRACE (x-popup-dialog);
1349   check_ns ();
1351   isQ = NILP (header);
1353   if (EQ (position, Qt)
1354       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1355                                || EQ (XCAR (position), Qtool_bar))))
1356     {
1357       window = selected_window;
1358     }
1359   else if (CONSP (position))
1360     {
1361       Lisp_Object tem;
1362       tem = Fcar (position);
1363       if (XTYPE (tem) == Lisp_Cons)
1364         window = Fcar (Fcdr (position));
1365       else
1366         {
1367           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1368           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1369         }
1370     }
1371   else if (WINDOWP (position) || FRAMEP (position))
1372     {
1373       window = position;
1374     }
1375   else
1376     window = Qnil;
1378   if (FRAMEP (window))
1379     f = XFRAME (window);
1380   else if (WINDOWP (window))
1381     {
1382       CHECK_LIVE_WINDOW (window);
1383       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1384     }
1385   else
1386     CHECK_WINDOW (window);
1388   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1389   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1391   BLOCK_INPUT;
1392   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1393                                            isQuestion: isQ];
1394   {
1395     int specpdl_count = SPECPDL_INDEX ();
1396     record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1397     popup_activated_flag = 1;
1398     tem = [dialog runDialogAt: p];
1399     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1400   }
1401   UNBLOCK_INPUT;
1403   return tem;
1407 /* ==========================================================================
1409     Popup Dialog: class implementation
1411    ========================================================================== */
1413 @interface FlippedView : NSView
1416 @end
1418 @implementation FlippedView
1419 - (BOOL)isFlipped
1421   return YES;
1423 @end
1425 @implementation EmacsDialogPanel
1427 #define SPACER          8.0
1428 #define ICONSIZE        64.0
1429 #define TEXTHEIGHT      20.0
1430 #define MINCELLWIDTH    90.0
1432 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1433               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1435   NSSize spacing = {SPACER, SPACER};
1436   NSRect area;
1437   char this_cmd_name[80];
1438   id cell;
1439   static NSImageView *imgView;
1440   static FlippedView *contentView;
1442   if (imgView == nil)
1443     {
1444       NSImage *img;
1445       area.origin.x   = 3*SPACER;
1446       area.origin.y   = 2*SPACER;
1447       area.size.width = ICONSIZE;
1448       area.size.height= ICONSIZE;
1449       img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1450       [img setScalesWhenResized: YES];
1451       [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1452       imgView = [[NSImageView alloc] initWithFrame: area];
1453       [imgView setImage: img];
1454       [imgView setEditable: NO];
1455       [img release];
1456     }
1458   aStyle = NSTitledWindowMask;
1459   flag = YES;
1460   rows = 0;
1461   cols = 1;
1462   [super initWithContentRect: contentRect styleMask: aStyle
1463                      backing: backingType defer: flag];
1464   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1465   [self setContentView: contentView];
1467   [[self contentView] setAutoresizesSubviews: YES];
1469   [[self contentView] addSubview: imgView];
1470   [self setTitle: @""];
1472   area.origin.x   += ICONSIZE+2*SPACER;
1473 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1474   area.size.width = 400;
1475   area.size.height= TEXTHEIGHT;
1476   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1477   [[self contentView] addSubview: command];
1478   [command setStringValue: ns_app_name];
1479   [command setDrawsBackground: NO];
1480   [command setBezeled: NO];
1481   [command setSelectable: NO];
1482   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1484 /*  area.origin.x   = ICONSIZE+2*SPACER;
1485   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1486   area.size.width = 400;
1487   area.size.height= 2;
1488   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1489   [[self contentView] addSubview: tem];
1490   [tem setTitlePosition: NSNoTitle];
1491   [tem setAutoresizingMask: NSViewWidthSizable];*/
1493 /*  area.origin.x = ICONSIZE+2*SPACER; */
1494   area.origin.y += TEXTHEIGHT+SPACER;
1495   area.size.width = 400;
1496   area.size.height= TEXTHEIGHT;
1497   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1498   [[self contentView] addSubview: title];
1499   [title setDrawsBackground: NO];
1500   [title setBezeled: NO];
1501   [title setSelectable: NO];
1502   [title setFont: [NSFont systemFontOfSize: 11.0]];
1504   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1505   [cell setBordered: NO];
1506   [cell setEnabled: NO];
1507   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1508   [cell setBezelStyle: NSRoundedBezelStyle];
1510   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1511                                       mode: NSHighlightModeMatrix
1512                                  prototype: cell
1513                               numberOfRows: 0
1514                            numberOfColumns: 1];
1515   [[self contentView] addSubview: matrix];
1516   [matrix release];
1517   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1518                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1519   [matrix setIntercellSpacing: spacing];
1521   [self setOneShot: YES];
1522   [self setReleasedWhenClosed: YES];
1523   [self setHidesOnDeactivate: YES];
1524   return self;
1528 - (BOOL)windowShouldClose: (id)sender
1530   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1531   return NO;
1535 void process_dialog (id window, Lisp_Object list)
1537   Lisp_Object item;
1538   int row = 0;
1540   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1541     {
1542       item = XCAR (list);
1543       if (XTYPE (item) == Lisp_String)
1544         {
1545           [window addString: SDATA (item) row: row++];
1546         }
1547       else if (XTYPE (item) == Lisp_Cons)
1548         {
1549           [window addButton: SDATA (XCAR (item))
1550                       value: XCDR (item) row: row++];
1551         }
1552       else if (NILP (item))
1553         {
1554           [window addSplit];
1555           row = 0;
1556         }
1557     }
1561 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1563   id cell;
1565   if (row >= rows)
1566     {
1567       [matrix addRow];
1568       rows++;
1569     }
1570   cell = [matrix cellAtRow: row column: cols-1];
1571   [cell setTarget: self];
1572   [cell setAction: @selector (clicked: )];
1573   [cell setTitle: [NSString stringWithUTF8String: str]];
1574   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1575   [cell setBordered: YES];
1576   [cell setEnabled: YES];
1578   return self;
1582 - addString: (char *)str row: (int)row
1584   id cell;
1586   if (row >= rows)
1587     {
1588       [matrix addRow];
1589       rows++;
1590     }
1591   cell = [matrix cellAtRow: row column: cols-1];
1592   [cell setTitle: [NSString stringWithUTF8String: str]];
1593   [cell setBordered: YES];
1594   [cell setEnabled: NO];
1596   return self;
1600 - addSplit
1602   [matrix addColumn];
1603   cols++;
1604   return self;
1608 - clicked: sender
1610   NSArray *sellist = nil;
1611   EMACS_INT seltag;
1613   sellist = [sender selectedCells];
1614   if ([sellist count]<1)
1615     return self;
1617   seltag = [[sellist objectAtIndex: 0] tag];
1618   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1619     [NSApp stopModalWithCode: seltag];
1620   return self;
1624 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1626   Lisp_Object head;
1627   [super init];
1629   if (XTYPE (contents) == Lisp_Cons)
1630     {
1631       head = Fcar (contents);
1632       process_dialog (self, Fcdr (contents));
1633     }
1634   else
1635     head = contents;
1637   if (XTYPE (head) == Lisp_String)
1638       [title setStringValue:
1639                  [NSString stringWithUTF8String: SDATA (head)]];
1640   else if (isQ == YES)
1641       [title setStringValue: @"Question"];
1642   else
1643       [title setStringValue: @"Information"];
1645   {
1646     int i;
1647     NSRect r, s, t;
1649     if (cols == 1 && rows > 1)  /* Never told where to split */
1650       {
1651         [matrix addColumn];
1652         for (i = 0; i<rows/2; i++)
1653           {
1654             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1655                       atRow: i column: 1];
1656             [matrix removeRow: (rows+1)/2];
1657           }
1658       }
1660     [matrix sizeToFit];
1661     {
1662       NSSize csize = [matrix cellSize];
1663       if (csize.width < MINCELLWIDTH)
1664         {
1665           csize.width = MINCELLWIDTH;
1666           [matrix setCellSize: csize];
1667           [matrix sizeToCells];
1668         }
1669     }
1671     [title sizeToFit];
1672     [command sizeToFit];
1674     t = [matrix frame];
1675     r = [title frame];
1676     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1677       {
1678         t.origin.x   = r.origin.x;
1679         t.size.width = r.size.width;
1680       }
1681     r = [command frame];
1682     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1683       {
1684         t.origin.x   = r.origin.x;
1685         t.size.width = r.size.width;
1686       }
1688     r = [self frame];
1689     s = [(NSView *)[self contentView] frame];
1690     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1691     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1692     [self setFrame: r display: NO];
1693   }
1695   return self;
1699 - (void)dealloc
1701   { [super dealloc]; return; };
1705 - (Lisp_Object)runDialogAt: (NSPoint)p
1707   NSInteger ret;
1709   /* initiate a session that will be ended by pop_down_menu */
1710   popupSession = [NSApp beginModalSessionForWindow: self];
1711   while (popup_activated_flag
1712          && (ret = [NSApp runModalSession: popupSession])
1713               == NSRunContinuesResponse)
1714     {
1715       /* Run this for timers.el, indep of atimers; might not return.
1716          TODO: use return value to avoid calling every iteration. */
1717       timer_check ();
1718       [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1719     }
1721   {                             /* FIXME: BIG UGLY HACK!!! */
1722       Lisp_Object tmp;
1723       *(EMACS_INT*)(&tmp) = ret;
1724       return tmp;
1725   }
1728 @end
1731 /* ==========================================================================
1733     Lisp definitions
1735    ========================================================================== */
1737 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1738        doc: /* Cause the NS menu to be re-calculated.  */)
1739      (void)
1741   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1742   return Qnil;
1746 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1747        doc: /* Pop up a dialog box and return user's selection.
1748 POSITION specifies which frame to use.
1749 This is normally a mouse button event or a window or frame.
1750 If POSITION is t, it means to use the frame the mouse is on.
1751 The dialog box appears in the middle of the specified frame.
1753 CONTENTS specifies the alternatives to display in the dialog box.
1754 It is a list of the form (DIALOG ITEM1 ITEM2...).
1755 Each ITEM is a cons cell (STRING . VALUE).
1756 The return value is VALUE from the chosen item.
1758 An ITEM may also be just a string--that makes a nonselectable item.
1759 An ITEM may also be nil--that means to put all preceding items
1760 on the left of the dialog box and all following items on the right.
1761 \(By default, approximately half appear on each side.)
1763 If HEADER is non-nil, the frame title for the box is "Information",
1764 otherwise it is "Question".
1766 If the user gets rid of the dialog box without making a valid choice,
1767 for instance using the window manager, then this produces a quit and
1768 `x-popup-dialog' does not return.  */)
1769      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1771   return ns_popup_dialog (position, contents, header);
1774 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1775        doc: /* Return t if a menu or popup dialog is active.  */)
1776      (void)
1778   return popup_activated () ? Qt : Qnil;
1781 /* ==========================================================================
1783     Lisp interface declaration
1785    ========================================================================== */
1787 void
1788 syms_of_nsmenu (void)
1790   defsubr (&Sx_popup_dialog);
1791   defsubr (&Sns_reset_menu);
1792   defsubr (&Smenu_or_popup_active_p);
1794   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1795   staticpro (&Qdebug_on_next_call);