Minor url.texi update.
[emacs.git] / src / nsmenu.m
blob9534aec8f2bcbfb3f8c998f2f22a004bda73b754
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007, 2008, 2009, 2010 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 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;
76 static NSModalSession popupSession;
78 /* NOTE: toolbar implementation is at end,
79   following complete menu implementation. */
82 /* ==========================================================================
84     Menu: Externally-called functions
86    ========================================================================== */
89 /* FIXME: not currently used, but should normalize with other terms. */
90 void
91 x_activate_menubar (struct frame *f)
93     fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
97 /* Supposed to discard menubar and free storage.  Since we share the
98    menubar among frames and update its context for the focused window,
99    there is nothing to do here. */
100 void
101 free_frame_menubar (struct frame *f)
103   return;
108 popup_activated (void)
110   return popup_activated_flag;
114 /* --------------------------------------------------------------------------
115     Update menubar.  Three cases:
116     1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
117        just top-level menu strings (OS X), or goto case (2) (GNUstep).
118     2) deep_p = 1, submenu = nil: Recompute all submenus.
119     3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
120    -------------------------------------------------------------------------- */
121 void
122 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
124   NSAutoreleasePool *pool;
125   id menu = [NSApp mainMenu];
126   static EmacsMenu *last_submenu = nil;
127   BOOL needsSet = NO;
128   const char *submenuTitle = [[submenu title] UTF8String];
129   extern int waiting_for_input;
130   int owfi;
131   Lisp_Object items;
132   widget_value *wv, *first_wv, *prev_wv = 0;
133   int i;
135 #if NSMENUPROFILE
136   struct timeb tb;
137   long t;
138 #endif
140   NSTRACE (set_frame_menubar);
142   if (f != SELECTED_FRAME ())
143       return;
144   XSETFRAME (Vmenu_updating_frame, f);
145 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
147   BLOCK_INPUT;
148   pool = [[NSAutoreleasePool alloc] init];
150   /* Menu may have been created automatically; if so, discard it. */
151   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
152     {
153       [menu release];
154       menu = nil;
155     }
157   if (menu == nil)
158     {
159       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
160       needsSet = YES;
161     }
162   else
163     {  /* close up anything on there */
164       id attMenu = [menu attachedMenu];
165       if (attMenu != nil)
166         [attMenu close];
167     }
169 #if NSMENUPROFILE
170   ftime (&tb);
171   t = -(1000*tb.time+tb.millitm);
172 #endif
174 #ifdef NS_IMPL_GNUSTEP
175   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
176 #endif
178   if (deep_p)
179     {
180       /* Fully parse one or more of the submenus. */
181       int n = 0;
182       int *submenu_start, *submenu_end;
183       int *submenu_top_level_items, *submenu_n_panes;
184       struct buffer *prev = current_buffer;
185       Lisp_Object buffer;
186       int specpdl_count = SPECPDL_INDEX ();
187       int previous_menu_items_used = f->menu_bar_items_used;
188       Lisp_Object *previous_items
189         = (Lisp_Object *) alloca (previous_menu_items_used
190                                   * sizeof (Lisp_Object));
192       /* lisp preliminaries */
193       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
194       specbind (Qinhibit_quit, Qt);
195       specbind (Qdebug_on_next_call, Qnil);
196       record_unwind_save_match_data ();
197       if (NILP (Voverriding_local_map_menu_flag))
198         {
199           specbind (Qoverriding_terminal_local_map, Qnil);
200           specbind (Qoverriding_local_map, Qnil);
201         }
202       set_buffer_internal_1 (XBUFFER (buffer));
204       /* TODO: for some reason this is not needed in other terms,
205            but some menu updates call Info-extract-pointer which causes
206            abort-on-error if waiting-for-input.  Needs further investigation. */
207       owfi = waiting_for_input;
208       waiting_for_input = 0;
210       /* lucid hook and possible reset */
211       safe_run_hooks (Qactivate_menubar_hook);
212       if (! NILP (Vlucid_menu_bar_dirty_flag))
213         call0 (Qrecompute_lucid_menubar);
214       safe_run_hooks (Qmenu_bar_update_hook);
215       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
217       /* Now ready to go */
218       items = FRAME_MENU_BAR_ITEMS (f);
220       /* Save the frame's previous menu bar contents data */
221       if (previous_menu_items_used)
222         memcpy (previous_items, XVECTOR (f->menu_bar_vector)->contents,
223                 previous_menu_items_used * sizeof (Lisp_Object));
225       /* parse stage 1: extract from lisp */
226       save_menu_items ();
228       menu_items = f->menu_bar_vector;
229       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
230       submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
231       submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
232       submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
233       submenu_top_level_items
234         = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
235       init_menu_items ();
236       for (i = 0; i < XVECTOR (items)->size; i += 4)
237         {
238           Lisp_Object key, string, maps;
240           key = XVECTOR (items)->contents[i];
241           string = XVECTOR (items)->contents[i + 1];
242           maps = XVECTOR (items)->contents[i + 2];
243           if (NILP (string))
244             break;
246           /* FIXME: we'd like to only parse the needed submenu, but this
247                was causing crashes in the _common parsing code.. need to make
248                sure proper initialization done.. */
249 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
250              continue; */
252           submenu_start[i] = menu_items_used;
254           menu_items_n_panes = 0;
255           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
256           submenu_n_panes[i] = menu_items_n_panes;
257           submenu_end[i] = menu_items_used;
258           n++;
259         }
261       finish_menu_items ();
262       waiting_for_input = owfi;
265       if (submenu && n == 0)
266         {
267           /* should have found a menu for this one but didn't */
268           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
269                   submenuTitle);
270           discard_menu_items ();
271           unbind_to (specpdl_count, Qnil);
272           [pool release];
273           UNBLOCK_INPUT;
274           return;
275         }
277       /* parse stage 2: insert into lucid 'widget_value' structures
278          [comments in other terms say not to evaluate lisp code here] */
279       wv = xmalloc_widget_value ();
280       wv->name = "menubar";
281       wv->value = 0;
282       wv->enabled = 1;
283       wv->button_type = BUTTON_TYPE_NONE;
284       wv->help = Qnil;
285       first_wv = wv;
287       for (i = 0; i < 4*n; i += 4)
288         {
289           menu_items_n_panes = submenu_n_panes[i];
290           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
291                                       submenu_top_level_items[i]);
292           if (prev_wv)
293             prev_wv->next = wv;
294           else
295             first_wv->contents = wv;
296           /* Don't set wv->name here; GC during the loop might relocate it.  */
297           wv->enabled = 1;
298           wv->button_type = BUTTON_TYPE_NONE;
299           prev_wv = wv;
300         }
302       set_buffer_internal_1 (prev);
304       /* Compare the new menu items with previous, and leave off if no change */
305       /* FIXME: following other terms here, but seems like this should be
306            done before parse stage 2 above, since its results aren't used */
307       if (previous_menu_items_used
308           && (!submenu || (submenu && submenu == last_submenu))
309           && menu_items_used == previous_menu_items_used)
310         {
311           for (i = 0; i < previous_menu_items_used; i++)
312             /* FIXME: this ALWAYS fails on Buffers menu items.. something
313                  about their strings causes them to change every time, so we
314                  double-check failures */
315             if (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i]))
316               if (!(STRINGP (previous_items[i])
317                     && STRINGP (XVECTOR (menu_items)->contents[i])
318                     && !strcmp (SDATA (previous_items[i]),
319                                SDATA (XVECTOR (menu_items)->contents[i]))))
320                   break;
321           if (i == previous_menu_items_used)
322             {
323               /* No change.. */
325 #if NSMENUPROFILE
326               ftime (&tb);
327               t += 1000*tb.time+tb.millitm;
328               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
329 #endif
331               free_menubar_widget_value_tree (first_wv);
332               discard_menu_items ();
333               unbind_to (specpdl_count, Qnil);
334               [pool release];
335               UNBLOCK_INPUT;
336               return;
337             }
338         }
339       /* The menu items are different, so store them in the frame */
340       /* FIXME: this is not correct for single-submenu case */
341       f->menu_bar_vector = menu_items;
342       f->menu_bar_items_used = menu_items_used;
344       /* Calls restore_menu_items, etc., as they were outside */
345       unbind_to (specpdl_count, Qnil);
347       /* Parse stage 2a: now GC cannot happen during the lifetime of the
348          widget_value, so it's safe to store data from a Lisp_String */
349       wv = first_wv->contents;
350       for (i = 0; i < XVECTOR (items)->size; i += 4)
351         {
352           Lisp_Object string;
353           string = XVECTOR (items)->contents[i + 1];
354           if (NILP (string))
355             break;
356 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
357                continue; */
359           wv->name = (char *) SDATA (string);
360           update_submenu_strings (wv->contents);
361           wv = wv->next;
362         }
364       /* Now, update the NS menu; if we have a submenu, use that, otherwise
365          create a new menu for each sub and fill it. */
366       if (submenu)
367         {
368           for (wv = first_wv->contents; wv; wv = wv->next)
369             {
370               if (!strcmp (submenuTitle, wv->name))
371                 {
372                   [submenu fillWithWidgetValue: wv->contents];
373                   last_submenu = submenu;
374                   break;
375                 }
376             }
377         }
378       else
379         {
380           [menu fillWithWidgetValue: first_wv->contents];
381         }
383     }
384   else
385     {
386       static int n_previous_strings = 0;
387       static char previous_strings[100][10];
388       static struct frame *last_f = NULL;
389       int n;
390       Lisp_Object string;
392       wv = xmalloc_widget_value ();
393       wv->name = "menubar";
394       wv->value = 0;
395       wv->enabled = 1;
396       wv->button_type = BUTTON_TYPE_NONE;
397       wv->help = Qnil;
398       first_wv = wv;
400       /* Make widget-value tree w/ just the top level menu bar strings */
401       items = FRAME_MENU_BAR_ITEMS (f);
402       if (NILP (items))
403         {
404           [pool release];
405           UNBLOCK_INPUT;
406           return;
407         }
410       /* check if no change.. this mechanism is a bit rough, but ready */
411       n = XVECTOR (items)->size / 4;
412       if (f == last_f && n_previous_strings == n)
413         {
414           for (i = 0; i<n; i++)
415             {
416               string = AREF (items, 4*i+1);
418               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
419                 continue;
420               if (NILP (string))
421                 if (previous_strings[i][0])
422                   break;
423               else
424                 continue;
425               if (strncmp (previous_strings[i], SDATA (string), 10))
426                 break;
427             }
429           if (i == n)
430             {
431               [pool release];
432               UNBLOCK_INPUT;
433               return;
434             }
435         }
437       [menu clear];
438       for (i = 0; i < XVECTOR (items)->size; i += 4)
439         {
440           string = XVECTOR (items)->contents[i + 1];
441           if (NILP (string))
442             break;
444           if (n < 100)
445             strncpy (previous_strings[i/4], SDATA (string), 10);
447           wv = xmalloc_widget_value ();
448           wv->name = (char *) SDATA (string);
449           wv->value = 0;
450           wv->enabled = 1;
451           wv->button_type = BUTTON_TYPE_NONE;
452           wv->help = Qnil;
453           wv->call_data = (void *) (EMACS_INT) (-1);
455 #ifdef NS_IMPL_COCOA
456           /* we'll update the real copy under app menu when time comes */
457           if (!strcmp ("Services", wv->name))
458             {
459               /* but we need to make sure it will update on demand */
460               [svcsMenu setFrame: f];
461               [svcsMenu setDelegate: svcsMenu];
462             }
463           else
464 #endif
465           [menu addSubmenuWithTitle: wv->name forFrame: f];
467           if (prev_wv)
468             prev_wv->next = wv;
469           else
470             first_wv->contents = wv;
471           prev_wv = wv;
472         }
474       last_f = f;
475       if (n < 100)
476         n_previous_strings = n;
477       else
478         n_previous_strings = 0;
480     }
481   free_menubar_widget_value_tree (first_wv);
484 #if NSMENUPROFILE
485   ftime (&tb);
486   t += 1000*tb.time+tb.millitm;
487   fprintf (stderr, "Menu update took %ld msec.\n", t);
488 #endif
490   /* set main menu */
491   if (needsSet)
492     [NSApp setMainMenu: menu];
494   [pool release];
495   UNBLOCK_INPUT;
500 /* Main emacs core entry point for menubar menus: called to indicate that the
501    frame's menus have changed, and the *step representation should be updated
502    from Lisp. */
503 void
504 set_frame_menubar (struct frame *f, int first_time, int deep_p)
506   ns_update_menubar (f, deep_p, nil);
510 /* Utility (from macmenu.c): is this item a separator? */
511 static int
512 name_is_separator ( const char *name)
514   const char *start = name;
516   /* Check if name string consists of only dashes ('-').  */
517   while (*name == '-') name++;
518   /* Separators can also be of the form "--:TripleSuperMegaEtched"
519      or "--deep-shadow".  We don't implement them yet, se we just treat
520      them like normal separators.  */
521   return (*name == '\0' || start + 2 == name);
525 /* ==========================================================================
527     Menu: class implementation
529    ========================================================================== */
532 /* Menu that can define itself from Emacs "widget_value"s and will lazily
533    update itself when user clicked.  Based on Carbon/AppKit implementation
534    by Yamamoto Mitsuharu. */
535 @implementation EmacsMenu
537 /* override designated initializer */
538 - initWithTitle: (NSString *)title
540   if (self = [super initWithTitle: title])
541     [self setAutoenablesItems: NO];
542   return self;
546 /* used for top-level */
547 - initWithTitle: (NSString *)title frame: (struct frame *)f
549   [self initWithTitle: title];
550   frame = f;
551 #ifdef NS_IMPL_COCOA
552   [self setDelegate: self];
553 #endif
554   return self;
558 - (void)setFrame: (struct frame *)f
560   frame = f;
564 /* delegate method called when a submenu is being opened: run a 'deep' call
565    to set_frame_menubar */
566 - (void)menuNeedsUpdate: (NSMenu *)menu
568   NSEvent *event;
569   if (!FRAME_LIVE_P (frame))
570     return;
571   event = [[FRAME_NS_VIEW (frame) window] currentEvent];
572   /* HACK: Cocoa/Carbon will request update on every keystroke
573      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
574      since key equivalents are handled through emacs.
575      On Leopard, even keystroke events generate SystemDefined events, but
576      their subtype is 8. */
577   if ([event type] != NSSystemDefined || [event subtype] == 8
578       /* Also, don't try this if from an event picked up asynchronously,
579          as lots of lisp evaluation happens in ns_update_menubar. */
580       || handling_signal != 0)
581     return;
582 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
583   ns_update_menubar (frame, 1, self);
587 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
589   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
590       && FRAME_NS_VIEW (SELECTED_FRAME ()))
591     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
592   return YES;
596 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
597    into an accelerator string.  We are only able to display a single character
598    for an accelerator, together with an optional modifier combination.  (Under
599    Carbon more control was possible, but in Cocoa multi-char strings passed to
600    NSMenuItem get ignored.  For now we try to display a super-single letter
601    combo, and return the others as strings to be appended to the item title.
602    (This is signaled by setting keyEquivModMask to 0 for now.) */
603 -(NSString *)parseKeyEquiv: (const char *)key
605   const char *tpos = key;
606   keyEquivModMask = NSCommandKeyMask;
608   if (!key || !strlen (key))
609     return @"";
610   
611   while (*tpos == ' ' || *tpos == '(')
612     tpos++;
613   if ((*tpos == 's') && (*(tpos+1) == '-'))
614     {
615       return [NSString stringWithFormat: @"%c", tpos[2]];
616     }
617   keyEquivModMask = 0; /* signal */
618   return [NSString stringWithUTF8String: tpos];
622 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
624   NSMenuItem *item;
625   widget_value *wv = (widget_value *)wvptr;
627   if (name_is_separator (wv->name))
628     {
629       item = [NSMenuItem separatorItem];
630       [self addItem: item];
631     }
632   else
633     {
634       NSString *title, *keyEq;
635       title = [NSString stringWithUTF8String: wv->name];
636       if (title == nil)
637         title = @"< ? >";  /* (get out in the open so we know about it) */
639       keyEq = [self parseKeyEquiv: wv->key];
640 #ifdef NS_IMPL_COCOA
641       /* OS X just ignores modifier strings longer than one character */
642       if (keyEquivModMask == 0)
643         title = [title stringByAppendingFormat: @" (%@)", keyEq];
644 #endif
646       item = [self addItemWithTitle: (NSString *)title
647                              action: @selector (menuDown:)
648                       keyEquivalent: keyEq];
649       [item setKeyEquivalentModifierMask: keyEquivModMask];
651       [item setEnabled: wv->enabled];
653       /* Draw radio buttons and tickboxes */
654       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
655                            wv->button_type == BUTTON_TYPE_RADIO))
656         [item setState: NSOnState];
657       else
658         [item setState: NSOffState];
660       [item setTag: (NSInteger)wv->call_data];
661     }
663   return item;
667 /* convenience */
668 -(void)clear
670   int n;
671   
672   for (n = [self numberOfItems]-1; n >= 0; n--)
673     {
674       NSMenuItem *item = [self itemAtIndex: n];
675       NSString *title = [item title];
676       if (([title length] == 0  /* OSX 10.5 */
677            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
678            || [@"Apple" isEqualToString: title]) /* older */
679           && ![item isSeparatorItem])
680         continue;
681       [self removeItemAtIndex: n];
682     }
686 - (void)fillWithWidgetValue: (void *)wvptr
688   widget_value *wv = (widget_value *)wvptr;
690   /* clear existing contents */
691   [self setMenuChangedMessagesEnabled: NO];
692   [self clear];
694   /* add new contents */
695   for (; wv != NULL; wv = wv->next)
696     {
697       NSMenuItem *item = [self addItemWithWidgetValue: wv];
699       if (wv->contents)
700         {
701           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
703           [self setSubmenu: submenu forItem: item];
704           [submenu fillWithWidgetValue: wv->contents];
705           [submenu release];
706           [item setAction: nil];
707         }
708     }
710   [self setMenuChangedMessagesEnabled: YES];
711 #ifdef NS_IMPL_GNUSTEP
712   if ([[self window] isVisible])
713     [self sizeToFit];
714 #else
715   if ([self supermenu] == nil)
716     [self sizeToFit];
717 #endif
721 /* adds an empty submenu and returns it */
722 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
724   NSString *titleStr = [NSString stringWithUTF8String: title];
725   NSMenuItem *item = [self addItemWithTitle: titleStr
726                                      action: nil /*@selector (menuDown:) */
727                               keyEquivalent: @""];
728   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
729   [self setSubmenu: submenu forItem: item];
730   [submenu release];
731   return submenu;
734 /* run a menu in popup mode */
735 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
736                  keymaps: (int)keymaps
738   EmacsView *view = FRAME_NS_VIEW (f);
739   NSEvent *e, *event;
740   long retVal;
742 /*   p = [view convertPoint:p fromView: nil]; */
743   p.y = NSHeight ([view frame]) - p.y;
744   e = [[view window] currentEvent];
745    event = [NSEvent mouseEventWithType: NSRightMouseDown
746                               location: p
747                          modifierFlags: 0
748                              timestamp: [e timestamp]
749                           windowNumber: [[view window] windowNumber]
750                                context: [e context]
751                            eventNumber: 0/*[e eventNumber] */
752                             clickCount: 1
753                               pressure: 0];
755   context_menu_value = -1;
756   [NSMenu popUpContextMenu: self withEvent: event forView: view];
757   retVal = context_menu_value;
758   context_menu_value = 0;
759   return retVal > 0
760       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
761       : Qnil;
764 @end  /* EmacsMenu */
768 /* ==========================================================================
770     Context Menu: implementing functions
772    ========================================================================== */
774 Lisp_Object
775 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
776               Lisp_Object title, const char **error)
778   EmacsMenu *pmenu;
779   NSPoint p;
780   Lisp_Object window, tem, keymap;
781   int specpdl_count = SPECPDL_INDEX ();
782   widget_value *wv, *first_wv = 0;
784   p.x = x; p.y = y;
786   /* now parse stage 2 as in ns_update_menubar */
787   wv = xmalloc_widget_value ();
788   wv->name = "contextmenu";
789   wv->value = 0;
790   wv->enabled = 1;
791   wv->button_type = BUTTON_TYPE_NONE;
792   wv->help = Qnil;
793   first_wv = wv;
795 #if 0
796   /* FIXME: a couple of one-line differences prevent reuse */
797   wv = digest_single_submenu (0, menu_items_used, Qnil);
798 #else
799   {
800   widget_value *save_wv = 0, *prev_wv = 0;
801   widget_value **submenu_stack
802     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
803 /*   Lisp_Object *subprefix_stack
804        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
805   int submenu_depth = 0;
806   int first_pane = 1;
807   int i;
809   /* Loop over all panes and items, filling in the tree.  */
810   i = 0;
811   while (i < menu_items_used)
812     {
813       if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
814         {
815           submenu_stack[submenu_depth++] = save_wv;
816           save_wv = prev_wv;
817           prev_wv = 0;
818           first_pane = 1;
819           i++;
820         }
821       else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
822         {
823           prev_wv = save_wv;
824           save_wv = submenu_stack[--submenu_depth];
825           first_pane = 0;
826           i++;
827         }
828       else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
829                && submenu_depth != 0)
830         i += MENU_ITEMS_PANE_LENGTH;
831       /* Ignore a nil in the item list.
832          It's meaningful only for dialog boxes.  */
833       else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
834         i += 1;
835       else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
836         {
837           /* Create a new pane.  */
838           Lisp_Object pane_name, prefix;
839           const char *pane_string;
841           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
842           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
844 #ifndef HAVE_MULTILINGUAL_MENU
845           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
846             {
847               pane_name = ENCODE_MENU_STRING (pane_name);
848               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
849             }
850 #endif
851           pane_string = (NILP (pane_name)
852                          ? "" : (char *) SDATA (pane_name));
853           /* If there is just one top-level pane, put all its items directly
854              under the top-level menu.  */
855           if (menu_items_n_panes == 1)
856             pane_string = "";
858           /* If the pane has a meaningful name,
859              make the pane a top-level menu item
860              with its items as a submenu beneath it.  */
861           if (!keymaps && strcmp (pane_string, ""))
862             {
863               wv = xmalloc_widget_value ();
864               if (save_wv)
865                 save_wv->next = wv;
866               else
867                 first_wv->contents = wv;
868               wv->name = pane_string;
869               if (keymaps && !NILP (prefix))
870                 wv->name++;
871               wv->value = 0;
872               wv->enabled = 1;
873               wv->button_type = BUTTON_TYPE_NONE;
874               wv->help = Qnil;
875               save_wv = wv;
876               prev_wv = 0;
877             }
878           else if (first_pane)
879             {
880               save_wv = wv;
881               prev_wv = 0;
882             }
883           first_pane = 0;
884           i += MENU_ITEMS_PANE_LENGTH;
885         }
886       else
887         {
888           /* Create a new item within current pane.  */
889           Lisp_Object item_name, enable, descrip, def, type, selected, help;
890           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
891           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
892           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
893           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
894           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
895           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
896           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
898 #ifndef HAVE_MULTILINGUAL_MENU
899           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
900             {
901               item_name = ENCODE_MENU_STRING (item_name);
902               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
903             }
905           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
906             {
907               descrip = ENCODE_MENU_STRING (descrip);
908               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
909             }
910 #endif /* not HAVE_MULTILINGUAL_MENU */
912           wv = xmalloc_widget_value ();
913           if (prev_wv)
914             prev_wv->next = wv;
915           else
916             save_wv->contents = wv;
917           wv->name = (char *) SDATA (item_name);
918           if (!NILP (descrip))
919             wv->key = (char *) SDATA (descrip);
920           wv->value = 0;
921           /* If this item has a null value,
922              make the call_data null so that it won't display a box
923              when the mouse is on it.  */
924           wv->call_data
925               = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
926           wv->enabled = !NILP (enable);
928           if (NILP (type))
929             wv->button_type = BUTTON_TYPE_NONE;
930           else if (EQ (type, QCtoggle))
931             wv->button_type = BUTTON_TYPE_TOGGLE;
932           else if (EQ (type, QCradio))
933             wv->button_type = BUTTON_TYPE_RADIO;
934           else
935             abort ();
937           wv->selected = !NILP (selected);
939           if (! STRINGP (help))
940             help = Qnil;
942           wv->help = help;
944           prev_wv = wv;
946           i += MENU_ITEMS_ITEM_LENGTH;
947         }
948     }
949   }
950 #endif
952   if (!NILP (title))
953     {
954       widget_value *wv_title = xmalloc_widget_value ();
955       widget_value *wv_sep = xmalloc_widget_value ();
957       /* Maybe replace this separator with a bitmap or owner-draw item
958          so that it looks better.  Having two separators looks odd.  */
959       wv_sep->name = "--";
960       wv_sep->next = first_wv->contents;
961       wv_sep->help = Qnil;
963 #ifndef HAVE_MULTILINGUAL_MENU
964       if (STRING_MULTIBYTE (title))
965         title = ENCODE_MENU_STRING (title);
966 #endif
968       wv_title->name = (char *) SDATA (title);
969       wv_title->enabled = NO;
970       wv_title->button_type = BUTTON_TYPE_NONE;
971       wv_title->help = Qnil;
972       wv_title->next = wv_sep;
973       first_wv->contents = wv_title;
974     }
976   pmenu = [[EmacsMenu alloc] initWithTitle:
977                                [NSString stringWithUTF8String: SDATA (title)]];
978   [pmenu fillWithWidgetValue: first_wv->contents];
979   free_menubar_widget_value_tree (first_wv);
980   unbind_to (specpdl_count, Qnil);
982   popup_activated_flag = 1;
983   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
984   popup_activated_flag = 0;
985   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
987   return tem;
991 /* ==========================================================================
993     Toolbar: externally-called functions
995    ========================================================================== */
997 void
998 free_frame_tool_bar (FRAME_PTR f)
999 /* --------------------------------------------------------------------------
1000     Under NS we just hide the toolbar until it might be needed again.
1001    -------------------------------------------------------------------------- */
1003   BLOCK_INPUT;
1004   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1005   FRAME_TOOLBAR_HEIGHT (f) = 0;
1006   UNBLOCK_INPUT;
1009 void
1010 update_frame_tool_bar (FRAME_PTR f)
1011 /* --------------------------------------------------------------------------
1012     Update toolbar contents
1013    -------------------------------------------------------------------------- */
1015   int i;
1016   EmacsView *view = FRAME_NS_VIEW (f);
1017   NSWindow *window = [view window];
1018   EmacsToolbar *toolbar = [view toolbar];
1020   BLOCK_INPUT;
1021   [toolbar clearActive];
1023   /* update EmacsToolbar as in GtkUtils, build items list */
1024   for (i = 0; i < f->n_tool_bar_items; ++i)
1025     {
1026 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1027                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1029       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1030       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1031       int idx;
1032       int img_id;
1033       struct image *img;
1034       Lisp_Object image;
1035       Lisp_Object helpObj;
1036       const char *helpText;
1038       /* If image is a vector, choose the image according to the
1039          button state.  */
1040       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1041       if (VECTORP (image))
1042         {
1043           /* NS toolbar auto-computes disabled and selected images */
1044           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1045           xassert (ASIZE (image) >= idx);
1046           image = AREF (image, idx);
1047         }
1048       else
1049         {
1050           idx = -1;
1051         }
1052       /* Ignore invalid image specifications.  */
1053       if (!valid_image_p (image))
1054         {
1055           NSLog (@"Invalid image for toolbar item");
1056           continue;
1057         }
1059       img_id = lookup_image (f, image);
1060       img = IMAGE_FROM_ID (f, img_id);
1061       prepare_image_for_display (f, img);
1063       if (img->load_failed_p || img->pixmap == nil)
1064         {
1065           NSLog (@"Could not prepare toolbar image for display.");
1066           continue;
1067         }
1069       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1070       if (NILP (helpObj))
1071         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1072       helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1074       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1075                                enabled: enabled_p];
1076 #undef TOOLPROP
1077     }
1079   if (![toolbar isVisible])
1080       [toolbar setVisible: YES];
1082   if ([toolbar changed])
1083     {
1084       /* inform app that toolbar has changed */
1085       NSDictionary *dict = [toolbar configurationDictionary];
1086       NSMutableDictionary *newDict = [dict mutableCopy];
1087       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1088       NSObject *key;
1089       while ((key = [keys nextObject]) != nil)
1090         {
1091           NSObject *val = [dict objectForKey: key];
1092           if ([val isKindOfClass: [NSArray class]])
1093             {
1094               [newDict setObject:
1095                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1096                           forKey: key];
1097               break;
1098             }
1099         }
1100       [toolbar setConfigurationFromDictionary: newDict];
1101       [newDict release];
1102     }
1104   FRAME_TOOLBAR_HEIGHT (f) =
1105     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1106     - FRAME_NS_TITLEBAR_HEIGHT (f);
1107   UNBLOCK_INPUT;
1111 /* ==========================================================================
1113     Toolbar: class implementation
1115    ========================================================================== */
1117 @implementation EmacsToolbar
1119 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1121   self = [super initWithIdentifier: identifier];
1122   emacsView = view;
1123   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1124   [self setSizeMode: NSToolbarSizeModeSmall];
1125   [self setDelegate: self];
1126   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1127   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1128   prevEnablement = enablement = 0L;
1129   return self;
1132 - (void)dealloc
1134   [prevIdentifiers release];
1135   [activeIdentifiers release];
1136   [identifierToItem release];
1137   [super dealloc];
1140 - (void) clearActive
1142   [prevIdentifiers release];
1143   prevIdentifiers = [activeIdentifiers copy];
1144   [activeIdentifiers removeAllObjects];
1145   prevEnablement = enablement;
1146   enablement = 0L;
1149 - (BOOL) changed
1151   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1152     enablement == prevEnablement ? NO : YES;
1155 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1156                         helpText: (const char *)help enabled: (BOOL)enabled
1158   /* 1) come up w/identifier */
1159   NSString *identifier
1160       = [NSString stringWithFormat: @"%u", [img hash]];
1162   /* 2) create / reuse item */
1163   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1164   if (item == nil)
1165     {
1166       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1167                autorelease];
1168       [item setImage: img];
1169       [item setToolTip: [NSString stringWithUTF8String: help]];
1170       [item setTarget: emacsView];
1171       [item setAction: @selector (toolbarClicked:)];
1172     }
1174   [item setTag: idx];
1175   [item setEnabled: enabled];
1177   /* 3) update state */
1178   [identifierToItem setObject: item forKey: identifier];
1179   [activeIdentifiers addObject: identifier];
1180   enablement = (enablement << 1) | (enabled == YES);
1183 /* This overrides super's implementation, which automatically sets
1184    all items to enabled state (for some reason). */
1185 - (void)validateVisibleItems { }
1188 /* delegate methods */
1190 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1191       itemForItemIdentifier: (NSString *)itemIdentifier
1192   willBeInsertedIntoToolbar: (BOOL)flag
1194   /* look up NSToolbarItem by identifier and return... */
1195   return [identifierToItem objectForKey: itemIdentifier];
1198 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1200   /* return entire set.. */
1201   return activeIdentifiers;
1204 /* for configuration palette (not yet supported) */
1205 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1207   /* return entire set... */
1208   return [identifierToItem allKeys];
1211 /* optional and unneeded */
1212 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1213 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1214 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1216 @end  /* EmacsToolbar */
1220 /* ==========================================================================
1222     Tooltip: class implementation
1224    ========================================================================== */
1226 /* Needed because NeXTstep does not provide enough control over tooltip
1227    display. */
1228 @implementation EmacsTooltip
1230 - init
1232   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1233                                             blue: 0.792 alpha: 0.95];
1234   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1235   NSFont *sfont = [font screenFont];
1236   int height = [sfont ascender] - [sfont descender];
1237 /*[font boundingRectForFont].size.height; */
1238   NSRect r = NSMakeRect (0, 0, 100, height+6);
1240   textField = [[NSTextField alloc] initWithFrame: r];
1241   [textField setFont: font];
1242   [textField setBackgroundColor: col];
1244   [textField setEditable: NO];
1245   [textField setSelectable: NO];
1246   [textField setBordered: YES];
1247   [textField setBezeled: YES];
1248   [textField setDrawsBackground: YES];
1250   win = [[NSWindow alloc]
1251             initWithContentRect: [textField frame]
1252                       styleMask: 0
1253                         backing: NSBackingStoreBuffered
1254                           defer: YES];
1255   [win setReleasedWhenClosed: NO];
1256   [win setDelegate: self];
1257   [[win contentView] addSubview: textField];
1258 /*  [win setBackgroundColor: col]; */
1259   [win setOpaque: NO];
1261   return self;
1264 - (void) dealloc
1266   [win close];
1267   [win release];
1268   [textField release];
1269   [super dealloc];
1272 - (void) setText: (char *)text
1274   NSString *str = [NSString stringWithUTF8String: text];
1275   NSRect r = [textField frame];
1276   NSSize textSize = [str sizeWithAttributes: 
1277      [NSDictionary dictionaryWithObject: [[textField font] screenFont]
1278                                  forKey: NSFontAttributeName]];
1279   NSSize padSize = [[[textField font] screenFont] 
1280                      boundingRectForFont].size;
1282   r.size.width = textSize.width + padSize.width/2;
1283   r.size.height = textSize.height + padSize.height/2;
1284   [textField setFrame: r];
1285   [textField setStringValue: str];
1288 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1290   NSRect wr = [win frame];
1292   wr.origin = NSMakePoint (x, y);
1293   wr.size = [textField frame].size;
1295   [win setFrame: wr display: YES];
1296   [win orderFront: self];
1297   [win display];
1298   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1299                                          selector: @selector (hide)
1300                                          userInfo: nil repeats: NO];
1301   [timer retain];
1304 - (void) hide
1306   [win close];
1307   if (timer != nil)
1308     {
1309       if ([timer isValid])
1310         [timer invalidate];
1311       [timer release];
1312       timer = nil;
1313     }
1316 - (BOOL) isActive
1318   return timer != nil;
1321 - (NSRect) frame
1323   return [textField frame];
1326 @end  /* EmacsTooltip */
1330 /* ==========================================================================
1332     Popup Dialog: implementing functions
1334    ========================================================================== */
1337 static Lisp_Object
1338 pop_down_menu (Lisp_Object arg)
1340   struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1341   if (popup_activated_flag)
1342     {
1343       popup_activated_flag = 0;
1344       BLOCK_INPUT;
1345       [NSApp endModalSession: popupSession];
1346       [((EmacsDialogPanel *) (p->pointer)) close];
1347       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1348       UNBLOCK_INPUT;
1349     }
1350   return Qnil;
1354 Lisp_Object
1355 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1357   id dialog;
1358   Lisp_Object window, tem;
1359   struct frame *f;
1360   NSPoint p;
1361   BOOL isQ;
1363   NSTRACE (x-popup-dialog);
1364   
1365   check_ns ();
1367   isQ = NILP (header);
1369   if (EQ (position, Qt)
1370       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1371                                || EQ (XCAR (position), Qtool_bar))))
1372     {
1373       window = selected_window;
1374     }
1375   else if (CONSP (position))
1376     {
1377       Lisp_Object tem;
1378       tem = Fcar (position);
1379       if (XTYPE (tem) == Lisp_Cons)
1380         window = Fcar (Fcdr (position));
1381       else
1382         {
1383           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1384           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1385         }
1386     }
1387   else if (WINDOWP (position) || FRAMEP (position))
1388     {
1389       window = position;
1390     }
1391   else
1392     window = Qnil;
1394   if (FRAMEP (window))
1395     f = XFRAME (window);
1396   else if (WINDOWP (window))
1397     {
1398       CHECK_LIVE_WINDOW (window);
1399       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1400     }
1401   else
1402     CHECK_WINDOW (window);
1404   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1405   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1407   BLOCK_INPUT;
1408   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1409                                            isQuestion: isQ];
1410   {
1411     int specpdl_count = SPECPDL_INDEX ();
1412     record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1413     popup_activated_flag = 1;
1414     tem = [dialog runDialogAt: p];
1415     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1416   }
1417   UNBLOCK_INPUT;
1419   return tem;
1423 /* ==========================================================================
1425     Popup Dialog: class implementation
1427    ========================================================================== */
1429 @interface FlippedView : NSView
1432 @end
1434 @implementation FlippedView
1435 - (BOOL)isFlipped
1437   return YES;
1439 @end
1441 @implementation EmacsDialogPanel
1443 #define SPACER          8.0
1444 #define ICONSIZE        64.0
1445 #define TEXTHEIGHT      20.0
1446 #define MINCELLWIDTH    90.0
1448 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1449               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1451   NSSize spacing = {SPACER, SPACER};
1452   NSRect area;
1453   char this_cmd_name[80];
1454   id cell;
1455   static NSImageView *imgView;
1456   static FlippedView *contentView;
1458   if (imgView == nil)
1459     {
1460       NSImage *img;
1461       area.origin.x   = 3*SPACER;
1462       area.origin.y   = 2*SPACER;
1463       area.size.width = ICONSIZE;
1464       area.size.height= ICONSIZE;
1465       img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1466       [img setScalesWhenResized: YES];
1467       [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1468       imgView = [[NSImageView alloc] initWithFrame: area];
1469       [imgView setImage: img];
1470       [imgView setEditable: NO];
1471       [img release];
1472     }
1474   aStyle = NSTitledWindowMask;
1475   flag = YES;
1476   rows = 0;
1477   cols = 1;
1478   [super initWithContentRect: contentRect styleMask: aStyle
1479                      backing: backingType defer: flag];
1480   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1481   [self setContentView: contentView];
1483   [[self contentView] setAutoresizesSubviews: YES];
1485   [[self contentView] addSubview: imgView];
1486   [self setTitle: @""];
1488   area.origin.x   += ICONSIZE+2*SPACER;
1489 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1490   area.size.width = 400;
1491   area.size.height= TEXTHEIGHT;
1492   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1493   [[self contentView] addSubview: command];
1494   [command setStringValue: ns_app_name];
1495   [command setDrawsBackground: NO];
1496   [command setBezeled: NO];
1497   [command setSelectable: NO];
1498   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1500 /*  area.origin.x   = ICONSIZE+2*SPACER;
1501   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1502   area.size.width = 400;
1503   area.size.height= 2;
1504   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1505   [[self contentView] addSubview: tem];
1506   [tem setTitlePosition: NSNoTitle];
1507   [tem setAutoresizingMask: NSViewWidthSizable];*/
1509 /*  area.origin.x = ICONSIZE+2*SPACER; */
1510   area.origin.y += TEXTHEIGHT+SPACER;
1511   area.size.width = 400;
1512   area.size.height= TEXTHEIGHT;
1513   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1514   [[self contentView] addSubview: title];
1515   [title setDrawsBackground: NO];
1516   [title setBezeled: NO];
1517   [title setSelectable: NO];
1518   [title setFont: [NSFont systemFontOfSize: 11.0]];
1520   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1521   [cell setBordered: NO];
1522   [cell setEnabled: NO];
1523   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1524   [cell setBezelStyle: NSRoundedBezelStyle];
1526   matrix = [[NSMatrix alloc] initWithFrame: contentRect 
1527                                       mode: NSHighlightModeMatrix 
1528                                  prototype: cell 
1529                               numberOfRows: 0 
1530                            numberOfColumns: 1];
1531   [[self contentView] addSubview: matrix];
1532   [matrix release];
1533   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1534                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1535   [matrix setIntercellSpacing: spacing];
1537   [self setOneShot: YES];
1538   [self setReleasedWhenClosed: YES];
1539   [self setHidesOnDeactivate: YES];
1540   return self;
1544 - (BOOL)windowShouldClose: (id)sender
1546   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1547   return NO;
1551 void process_dialog (id window, Lisp_Object list)
1553   Lisp_Object item;
1554   int row = 0;
1556   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1557     {
1558       item = XCAR (list);
1559       if (XTYPE (item) == Lisp_String)
1560         {
1561           [window addString: SDATA (item) row: row++];
1562         }
1563       else if (XTYPE (item) == Lisp_Cons)
1564         {
1565           [window addButton: SDATA (XCAR (item))
1566                       value: XCDR (item) row: row++];
1567         }
1568       else if (NILP (item))
1569         {
1570           [window addSplit];
1571           row = 0;
1572         }
1573     }
1577 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1579   id cell;
1580        
1581   if (row >= rows)
1582     {
1583       [matrix addRow];
1584       rows++;
1585     }
1586   cell = [matrix cellAtRow: row column: cols-1];
1587   [cell setTarget: self];
1588   [cell setAction: @selector (clicked: )];
1589   [cell setTitle: [NSString stringWithUTF8String: str]];
1590   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1591   [cell setBordered: YES];
1592   [cell setEnabled: YES];
1594   return self;
1598 - addString: (char *)str row: (int)row
1600   id cell;
1601        
1602   if (row >= rows)
1603     {
1604       [matrix addRow];
1605       rows++;
1606     }
1607   cell = [matrix cellAtRow: row column: cols-1];
1608   [cell setTitle: [NSString stringWithUTF8String: str]];
1609   [cell setBordered: YES];
1610   [cell setEnabled: NO];
1612   return self;
1616 - addSplit
1618   [matrix addColumn];
1619   cols++;
1620   return self;
1624 - clicked: sender
1626   NSArray *sellist = nil;
1627   EMACS_INT seltag;
1629   sellist = [sender selectedCells];
1630   if ([sellist count]<1) 
1631     return self;
1633   seltag = [[sellist objectAtIndex: 0] tag];
1634   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1635     [NSApp stopModalWithCode: seltag];
1636   return self;
1640 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1642   Lisp_Object head;
1643   [super init];
1645   if (XTYPE (contents) == Lisp_Cons)
1646     {
1647       head = Fcar (contents);
1648       process_dialog (self, Fcdr (contents));
1649     }
1650   else
1651     head = contents;
1653   if (XTYPE (head) == Lisp_String)
1654       [title setStringValue:
1655                  [NSString stringWithUTF8String: SDATA (head)]];
1656   else if (isQ == YES)
1657       [title setStringValue: @"Question"];
1658   else
1659       [title setStringValue: @"Information"];
1661   {
1662     int i;
1663     NSRect r, s, t;
1665     if (cols == 1 && rows > 1)  /* Never told where to split */
1666       {
1667         [matrix addColumn];
1668         for (i = 0; i<rows/2; i++)
1669           {
1670             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1671                       atRow: i column: 1];
1672             [matrix removeRow: (rows+1)/2];
1673           }
1674       }
1676     [matrix sizeToFit];
1677     {
1678       NSSize csize = [matrix cellSize];
1679       if (csize.width < MINCELLWIDTH)
1680         {
1681           csize.width = MINCELLWIDTH;
1682           [matrix setCellSize: csize];
1683           [matrix sizeToCells];
1684         }
1685     }
1687     [title sizeToFit];
1688     [command sizeToFit];
1690     t = [matrix frame];
1691     r = [title frame];
1692     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1693       {
1694         t.origin.x   = r.origin.x;
1695         t.size.width = r.size.width;
1696       }
1697     r = [command frame];
1698     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1699       {
1700         t.origin.x   = r.origin.x;
1701         t.size.width = r.size.width;
1702       }
1704     r = [self frame];
1705     s = [(NSView *)[self contentView] frame];
1706     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1707     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1708     [self setFrame: r display: NO];
1709   }
1711   return self;
1715 - (void)dealloc
1717   { [super dealloc]; return; };
1721 - (Lisp_Object)runDialogAt: (NSPoint)p
1723   NSInteger ret;
1724   extern EMACS_TIME timer_check (int do_it_now); /* TODO: add to a header */
1726   /* initiate a session that will be ended by pop_down_menu */
1727   popupSession = [NSApp beginModalSessionForWindow: self];
1728   while (popup_activated_flag
1729          && (ret = [NSApp runModalSession: popupSession])
1730               == NSRunContinuesResponse)
1731     {
1732       /* Run this for timers.el, indep of atimers; might not return.
1733          TODO: use return value to avoid calling every iteration. */
1734       timer_check (1);
1735       [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1736     }
1738   {                             /* FIXME: BIG UGLY HACK!!! */
1739       Lisp_Object tmp;
1740       *(EMACS_INT*)(&tmp) = ret;
1741       return tmp;
1742   }
1745 @end
1748 /* ==========================================================================
1750     Lisp definitions
1752    ========================================================================== */
1754 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1755        doc: /* Cause the NS menu to be re-calculated.  */)
1756      (void)
1758   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1759   return Qnil;
1763 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1764        doc: /* Pop up a dialog box and return user's selection.
1765 POSITION specifies which frame to use.
1766 This is normally a mouse button event or a window or frame.
1767 If POSITION is t, it means to use the frame the mouse is on.
1768 The dialog box appears in the middle of the specified frame.
1770 CONTENTS specifies the alternatives to display in the dialog box.
1771 It is a list of the form (DIALOG ITEM1 ITEM2...).
1772 Each ITEM is a cons cell (STRING . VALUE).
1773 The return value is VALUE from the chosen item.
1775 An ITEM may also be just a string--that makes a nonselectable item.
1776 An ITEM may also be nil--that means to put all preceding items
1777 on the left of the dialog box and all following items on the right.
1778 \(By default, approximately half appear on each side.)
1780 If HEADER is non-nil, the frame title for the box is "Information",
1781 otherwise it is "Question".
1783 If the user gets rid of the dialog box without making a valid choice,
1784 for instance using the window manager, then this produces a quit and
1785 `x-popup-dialog' does not return.  */)
1786      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1788   return ns_popup_dialog (position, contents, header);
1791 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1792        doc: /* Return t if a menu or popup dialog is active.  */)
1793      (void)
1795   return popup_activated () ? Qt : Qnil;
1798 /* ==========================================================================
1800     Lisp interface declaration
1802    ========================================================================== */
1804 void
1805 syms_of_nsmenu (void)
1807   defsubr (&Sx_popup_dialog);
1808   defsubr (&Sns_reset_menu);
1809   defsubr (&Smenu_or_popup_active_p);
1811   Qdebug_on_next_call = intern ("debug-on-next-call");
1812   staticpro (&Qdebug_on_next_call);
1815 // arch-tag: 75773656-52e5-4c44-a398-47bd87b32619