* keyboard.c (Fset_input_meta_mode): Doc fix.
[emacs.git] / src / nsmenu.m
blob190bbb3762400308b8001728659b1c096d4e2558
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007, 2008, 2009 Free Software Foundation, Inc.
4 This file is part of GNU Emacs.
6 GNU Emacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 GNU Emacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
20 By Adrian Robert, based on code from original nsmenu.m (Carl Edman,
21 Christian Limpach, Scott Bender, Christophe de Dinechin) and code in the
22 Carbon version by Yamamoto Mitsuharu. */
24 /* This should be the first include, as it may set up #defines affecting
25    interpretation of even the system includes. */
26 #include "config.h"
28 #include "lisp.h"
29 #include "window.h"
30 #include "buffer.h"
31 #include "keymap.h"
32 #include "coding.h"
33 #include "commands.h"
34 #include "blockinput.h"
35 #include "nsterm.h"
36 #include "termhooks.h"
37 #include "keyboard.h"
39 #define NSMENUPROFILE 0
41 #if NSMENUPROFILE
42 #include <sys/timeb.h>
43 #include <sys/types.h>
44 #endif
46 #define MenuStagger 10.0
48 #if 0
49 int menu_trace_num = 0;
50 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
51                                 __FILE__, __LINE__, ++menu_trace_num)
52 #else
53 #define NSTRACE(x)
54 #endif
56 #if 0
57 /* Include lisp -> C common menu parsing code */
58 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
59 #include "nsmenu_common.c"
60 #endif
62 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
63 extern Lisp_Object QCtoggle, QCradio;
65 extern Lisp_Object Vmenu_updating_frame;
67 Lisp_Object Qdebug_on_next_call;
68 extern Lisp_Object Voverriding_local_map, Voverriding_local_map_menu_flag,
69                    Qoverriding_local_map, Qoverriding_terminal_local_map;
71 extern long context_menu_value;
72 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
74 /* Nonzero means a menu is currently active.  */
75 static int popup_activated_flag;
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 ()
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: @"Emacs"];
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   /* widget_value is a straightforward object translation of emacs's
175      Byzantine lisp menu structures */
176   wv = xmalloc_widget_value ();
177   wv->name = "Emacs";
178   wv->value = 0;
179   wv->enabled = 1;
180   wv->button_type = BUTTON_TYPE_NONE;
181   wv->help = Qnil;
182   first_wv = wv;
184 #ifdef NS_IMPL_GNUSTEP
185   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
186 #endif
188   if (deep_p)
189     {
190       /* Fully parse one or more of the submenus. */
191       int n = 0;
192       int *submenu_start, *submenu_end;
193       int *submenu_top_level_items, *submenu_n_panes;
194       struct buffer *prev = current_buffer;
195       Lisp_Object buffer;
196       int specpdl_count = SPECPDL_INDEX ();
197       int previous_menu_items_used = f->menu_bar_items_used;
198       Lisp_Object *previous_items
199         = (Lisp_Object *) alloca (previous_menu_items_used
200                                   * sizeof (Lisp_Object));
202       /* lisp preliminaries */
203       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
204       specbind (Qinhibit_quit, Qt);
205       specbind (Qdebug_on_next_call, Qnil);
206       record_unwind_save_match_data ();
207       if (NILP (Voverriding_local_map_menu_flag))
208         {
209           specbind (Qoverriding_terminal_local_map, Qnil);
210           specbind (Qoverriding_local_map, Qnil);
211         }
212       set_buffer_internal_1 (XBUFFER (buffer));
214       /* TODO: for some reason this is not needed in other terms,
215            but some menu updates call Info-extract-pointer which causes
216            abort-on-error if waiting-for-input.  Needs further investigation. */
217       owfi = waiting_for_input;
218       waiting_for_input = 0;
220       /* lucid hook and possible reset */
221       safe_run_hooks (Qactivate_menubar_hook);
222       if (! NILP (Vlucid_menu_bar_dirty_flag))
223         call0 (Qrecompute_lucid_menubar);
224       safe_run_hooks (Qmenu_bar_update_hook);
225       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
227       /* Now ready to go */
228       items = FRAME_MENU_BAR_ITEMS (f);
230       /* Save the frame's previous menu bar contents data */
231       if (previous_menu_items_used)
232         bcopy (XVECTOR (f->menu_bar_vector)->contents, previous_items,
233                previous_menu_items_used * sizeof (Lisp_Object));
235       /* parse stage 1: extract from lisp */
236       save_menu_items ();
238       menu_items = f->menu_bar_vector;
239       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
240       submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
241       submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
242       submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
243       submenu_top_level_items
244         = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
245       init_menu_items ();
246       for (i = 0; i < XVECTOR (items)->size; i += 4)
247         {
248           Lisp_Object key, string, maps;
250           key = XVECTOR (items)->contents[i];
251           string = XVECTOR (items)->contents[i + 1];
252           maps = XVECTOR (items)->contents[i + 2];
253           if (NILP (string))
254             break;
256           /* FIXME: we'd like to only parse the needed submenu, but this
257                was causing crashes in the _common parsing code.. need to make
258                sure proper initialization done.. */
259 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
260              continue; */
262           submenu_start[i] = menu_items_used;
264           menu_items_n_panes = 0;
265           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
266           submenu_n_panes[i] = menu_items_n_panes;
267           submenu_end[i] = menu_items_used;
268           n++;
269         }
271       finish_menu_items ();
272       waiting_for_input = owfi;
275       if (submenu && n == 0)
276         {
277           /* should have found a menu for this one but didn't */
278           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
279                   submenuTitle);
280           discard_menu_items ();
281           unbind_to (specpdl_count, Qnil);
282           [pool release];
283           UNBLOCK_INPUT;
284           return;
285         }
287       /* parse stage 2: insert into lucid 'widget_value' structures
288          [comments in other terms say not to evaluate lisp code here] */
289       wv = xmalloc_widget_value ();
290       wv->name = "menubar";
291       wv->value = 0;
292       wv->enabled = 1;
293       wv->button_type = BUTTON_TYPE_NONE;
294       wv->help = Qnil;
295       first_wv = wv;
297       for (i = 0; i < 4*n; i += 4)
298         {
299           menu_items_n_panes = submenu_n_panes[i];
300           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
301                                       submenu_top_level_items[i]);
302           if (prev_wv)
303             prev_wv->next = wv;
304           else
305             first_wv->contents = wv;
306           /* Don't set wv->name here; GC during the loop might relocate it.  */
307           wv->enabled = 1;
308           wv->button_type = BUTTON_TYPE_NONE;
309           prev_wv = wv;
310         }
312       set_buffer_internal_1 (prev);
314       /* Compare the new menu items with previous, and leave off if no change */
315       /* FIXME: following other terms here, but seems like this should be
316            done before parse stage 2 above, since its results aren't used */
317       if (previous_menu_items_used
318           && (!submenu || (submenu && submenu == last_submenu))
319           && menu_items_used == previous_menu_items_used)
320         {
321           for (i = 0; i < previous_menu_items_used; i++)
322             /* FIXME: this ALWAYS fails on Buffers menu items.. something
323                  about their strings causes them to change every time, so we
324                  double-check failures */
325             if (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i]))
326               if (!(STRINGP (previous_items[i])
327                     && STRINGP (XVECTOR (menu_items)->contents[i])
328                     && !strcmp (SDATA (previous_items[i]),
329                                SDATA (XVECTOR (menu_items)->contents[i]))))
330                   break;
331           if (i == previous_menu_items_used)
332             {
333               /* No change.. */
335 #if NSMENUPROFILE
336               ftime (&tb);
337               t += 1000*tb.time+tb.millitm;
338               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
339 #endif
341               free_menubar_widget_value_tree (first_wv);
342               discard_menu_items ();
343               unbind_to (specpdl_count, Qnil);
344               [pool release];
345               UNBLOCK_INPUT;
346               return;
347             }
348         }
349       /* The menu items are different, so store them in the frame */
350       /* FIXME: this is not correct for single-submenu case */
351       f->menu_bar_vector = menu_items;
352       f->menu_bar_items_used = menu_items_used;
354       /* Calls restore_menu_items, etc., as they were outside */
355       unbind_to (specpdl_count, Qnil);
357       /* Parse stage 2a: now GC cannot happen during the lifetime of the
358          widget_value, so it's safe to store data from a Lisp_String */
359       wv = first_wv->contents;
360       for (i = 0; i < XVECTOR (items)->size; i += 4)
361         {
362           Lisp_Object string;
363           string = XVECTOR (items)->contents[i + 1];
364           if (NILP (string))
365             break;
366 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
367                continue; */
369           wv->name = (char *) SDATA (string);
370           update_submenu_strings (wv->contents);
371           wv = wv->next;
372         }
374       /* Now, update the NS menu; if we have a submenu, use that, otherwise
375          create a new menu for each sub and fill it. */
376       if (submenu)
377         {
378           for (wv = first_wv->contents; wv; wv = wv->next)
379             {
380               if (!strcmp (submenuTitle, wv->name))
381                 {
382                   [submenu fillWithWidgetValue: wv->contents];
383                   last_submenu = submenu;
384                   break;
385                 }
386             }
387         }
388       else
389         {
390           [menu fillWithWidgetValue: first_wv->contents];
391         }
393     }
394   else
395     {
396       static int n_previous_strings = 0;
397       static char previous_strings[100][10];
398       static struct frame *last_f = NULL;
399       int n;
400       Lisp_Object string;
402       /* Make widget-value tree w/ just the top level menu bar strings */
403       items = FRAME_MENU_BAR_ITEMS (f);
404       if (NILP (items))
405         {
406           [pool release];
407           UNBLOCK_INPUT;
408           return;
409         }
412       /* check if no change.. this mechanism is a bit rough, but ready */
413       n = XVECTOR (items)->size / 4;
414       if (f == last_f && n_previous_strings == n)
415         {
416           for (i = 0; i<n; i++)
417             {
418               string = AREF (items, 4*i+1);
420               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
421                 continue;
422               if (NILP (string))
423                 if (previous_strings[i][0])
424                   break;
425               else
426                 continue;
427               if (strncmp (previous_strings[i], SDATA (string), 10))
428                 break;
429             }
431           if (i == n)
432             {
433               [pool release];
434               UNBLOCK_INPUT;
435               return;
436             }
437         }
439       [menu clear];
440       for (i = 0; i < XVECTOR (items)->size; i += 4)
441         {
442           string = XVECTOR (items)->contents[i + 1];
443           if (NILP (string))
444             break;
446           if (n < 100)
447             strncpy (previous_strings[i/4], SDATA (string), 10);
449           wv = xmalloc_widget_value ();
450           wv->name = (char *) SDATA (string);
451           wv->value = 0;
452           wv->enabled = 1;
453           wv->button_type = BUTTON_TYPE_NONE;
454           wv->help = Qnil;
455           wv->call_data = (void *) (EMACS_INT) (-1);
457 #ifdef NS_IMPL_COCOA
458           /* we'll update the real copy under app menu when time comes */
459           if (!strcmp ("Services", wv->name))
460             {
461               /* but we need to make sure it will update on demand */
462               [svcsMenu setFrame: f];
463               [svcsMenu setDelegate: svcsMenu];
464             }
465           else
466 #endif
467           [menu addSubmenuWithTitle: wv->name forFrame: f];
469           if (prev_wv)
470             prev_wv->next = wv;
471           else
472             first_wv->contents = wv;
473           prev_wv = wv;
474         }
476       last_f = f;
477       if (n < 100)
478         n_previous_strings = n;
479       else
480         n_previous_strings = 0;
482     }
483   free_menubar_widget_value_tree (first_wv);
486 #if NSMENUPROFILE
487   ftime (&tb);
488   t += 1000*tb.time+tb.millitm;
489   fprintf (stderr, "Menu update took %ld msec.\n", t);
490 #endif
492   /* set main menu */
493   if (needsSet)
494     [NSApp setMainMenu: menu];
496   [pool release];
497   UNBLOCK_INPUT;
502 /* Main emacs core entry point for menubar menus: called to indicate that the
503    frame's menus have changed, and the *step representation should be updated
504    from Lisp. */
505 void
506 set_frame_menubar (struct frame *f, int first_time, int deep_p)
508   ns_update_menubar (f, deep_p, nil);
512 /* Utility (from macmenu.c): is this item a separator? */
513 static int
514 name_is_separator (name)
515      const char *name;
517   const char *start = name;
519   /* Check if name string consists of only dashes ('-').  */
520   while (*name == '-') name++;
521   /* Separators can also be of the form "--:TripleSuperMegaEtched"
522      or "--deep-shadow".  We don't implement them yet, se we just treat
523      them like normal separators.  */
524   return (*name == '\0' || start + 2 == name);
528 /* ==========================================================================
530     Menu: class implementation
532    ========================================================================== */
535 /* Menu that can define itself from Emacs "widget_value"s and will lazily
536    update itself when user clicked.  Based on Carbon/AppKit implementation
537    by Yamamoto Mitsuharu. */
538 @implementation EmacsMenu
540 /* override designated initializer */
541 - initWithTitle: (NSString *)title
543   if (self = [super initWithTitle: title])
544     [self setAutoenablesItems: NO];
545   return self;
549 /* used for top-level */
550 - initWithTitle: (NSString *)title frame: (struct frame *)f
552   [self initWithTitle: title];
553   frame = f;
554 #ifdef NS_IMPL_COCOA
555   [self setDelegate: self];
556 #endif
557   return self;
561 - (void)setFrame: (struct frame *)f
563   frame = f;
567 /* delegate method called when a submenu is being opened: run a 'deep' call
568    to set_frame_menubar */
569 - (void)menuNeedsUpdate: (NSMenu *)menu
571   NSEvent *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: (char *)key
605   char *tpos = key;
606   keyEquivModMask = NSCommandKeyMask;
608   if (!key || !strlen (key))
609     return @"";
610   
611   while (*tpos == ' ' || *tpos == '(')
612     tpos++;
613   if (*tpos != 's') {
614     keyEquivModMask = 0; /* signal */
615     return [NSString stringWithUTF8String: tpos];
616   }
617   return [NSString stringWithFormat: @"%c", tpos[2]];
621 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
623   NSMenuItem *item;
624   widget_value *wv = (widget_value *)wvptr;
626   if (name_is_separator (wv->name))
627     {
628       item = [NSMenuItem separatorItem];
629       [self addItem: item];
630     }
631   else
632     {
633       NSString *title, *keyEq;
634       title = [NSString stringWithUTF8String: wv->name];
635       if (title == nil)
636         title = @"< ? >";  /* (get out in the open so we know about it) */
638       keyEq = [self parseKeyEquiv: wv->key];
639 #ifdef NS_IMPL_COCOA
640       /* OS X just ignores modifier strings longer than one character */
641       if (keyEquivModMask == 0)
642         title = [title stringByAppendingFormat: @" (%@)", keyEq];
643 #endif
645       item = [self addItemWithTitle: (NSString *)title
646                              action: @selector (menuDown:)
647                       keyEquivalent: keyEq];
648       [item setKeyEquivalentModifierMask: keyEquivModMask];
650       [item setEnabled: wv->enabled];
652       /* Draw radio buttons and tickboxes */
653       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
654                            wv->button_type == BUTTON_TYPE_RADIO))
655         [item setState: NSOnState];
656       else
657         [item setState: NSOffState];
659       [item setTag: (int)wv->call_data];
660     }
662   return item;
666 /* convenience */
667 -(void) clear
669   int n;
670   
671   for (n = [self numberOfItems]-1; n >= 0; n--)
672     {
673       NSMenuItem *item = [self itemAtIndex: n];
674       NSString *title = [item title];
675       if (([title length] == 0 || [@"Apple" isEqualToString: title])
676           && ![item isSeparatorItem])
677         continue;
678       [self removeItemAtIndex: n];
679     }
683 - (void)fillWithWidgetValue: (void *)wvptr
685   widget_value *wv = (widget_value *)wvptr;
687   /* clear existing contents */
688   [self setMenuChangedMessagesEnabled: NO];
689   [self clear];
691   /* add new contents */
692   for (; wv != NULL; wv = wv->next)
693     {
694       NSMenuItem *item = [self addItemWithWidgetValue: wv];
696       if (wv->contents)
697         {
698           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
700           [self setSubmenu: submenu forItem: item];
701           [submenu fillWithWidgetValue: wv->contents];
702           [submenu release];
703           [item setAction: nil];
704         }
705     }
707   [self setMenuChangedMessagesEnabled: YES];
708 #ifdef NS_IMPL_GNUSTEP
709   if ([[self window] isVisible])
710     [self sizeToFit];
711 #else
712   if ([self supermenu] == nil)
713     [self sizeToFit];
714 #endif
718 /* adds an empty submenu and returns it */
719 - (EmacsMenu *)addSubmenuWithTitle: (char *)title forFrame: (struct frame *)f
721   NSString *titleStr = [NSString stringWithUTF8String: title];
722   NSMenuItem *item = [self addItemWithTitle: titleStr
723                                      action: nil /*@selector (menuDown:) */
724                               keyEquivalent: @""];
725   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
726   [self setSubmenu: submenu forItem: item];
727   [submenu release];
728   return submenu;
731 /* run a menu in popup mode */
732 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
733                  keymaps: (int)keymaps
735   EmacsView *view = FRAME_NS_VIEW (f);
736 /*   p = [view convertPoint:p fromView: nil]; */
737   p.y = NSHeight ([view frame]) - p.y;
738   NSEvent *e = [[view window] currentEvent];
739   NSEvent *event = [NSEvent mouseEventWithType: NSRightMouseDown
740                                       location: p
741                                  modifierFlags: 0
742                                      timestamp: [e timestamp]
743                                   windowNumber: [[view window] windowNumber]
744                                        context: [e context]
745                                    eventNumber: 0/*[e eventNumber] */
746                                     clickCount: 1
747                                       pressure: 0];
748   long retVal;
750   context_menu_value = -1;
751   [NSMenu popUpContextMenu: self withEvent: event forView: view];
752   retVal = context_menu_value;
753   context_menu_value = 0;
754   return retVal > 0
755       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
756       : Qnil;
759 @end  /* EmacsMenu */
763 /* ==========================================================================
765     Context Menu: implementing functions
767    ========================================================================== */
769 static Lisp_Object
770 cleanup_popup_menu (Lisp_Object arg)
772   discard_menu_items ();
773   return Qnil;
777 static Lisp_Object
778 ns_popup_menu (Lisp_Object position, Lisp_Object menu)
780   EmacsMenu *pmenu;
781   struct frame *f = NULL;
782   NSPoint p;
783   Lisp_Object window, x, y, tem, keymap, title;
784   struct gcpro gcpro1;
785   int specpdl_count = SPECPDL_INDEX (), specpdl_count2;
786   char *error_name = NULL;
787   int keymaps = 0;
788   widget_value *wv, *first_wv = 0;
790   NSTRACE (ns_popup_menu);
792   if (!NILP (position))
793     {
794       check_ns ();
795   
796       if (EQ (position, Qt)
797           || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
798                                    || EQ (XCAR (position), Qtool_bar))))
799         {
800           /* Use the mouse's current position.  */
801           struct frame *new_f = SELECTED_FRAME ();
803           if (FRAME_TERMINAL (new_f)->mouse_position_hook)
804             (*FRAME_TERMINAL (new_f)->mouse_position_hook)
805               (&new_f, 0, 0, 0, &x, &y, 0);
806           if (new_f != 0)
807             XSETFRAME (window, new_f);
808           else
809             {
810               window = selected_window;
811               x = make_number (0);
812               y = make_number (0);
813             }
814         }
815       else
816         {
817           CHECK_CONS (position);
818           tem = Fcar (position);
819           if (XTYPE (tem) == Lisp_Cons)
820             {
821               window = Fcar (Fcdr (position));
822               x = Fcar (tem);
823               y = Fcar (Fcdr (tem));
824             }
825           else
826             {
827               tem = Fcar (Fcdr (position));
828               window = Fcar (tem);
829               tem = Fcar (Fcdr (Fcdr (tem)));
830               x = Fcar (tem);
831               y = Fcdr (tem);
832             }
833         }
834   
835       CHECK_NUMBER (x);
836       CHECK_NUMBER (y);
838       if (FRAMEP (window))
839         {
840           f = XFRAME (window);
841       
842           p.x = 0;
843           p.y = 0;
844         }
845       else
846         {
847           struct window *win = XWINDOW (window);
848           CHECK_LIVE_WINDOW (window);
849           f = XFRAME (WINDOW_FRAME (win));
850           p.x = FRAME_COLUMN_WIDTH (f) * WINDOW_LEFT_EDGE_COL (win);
851           p.y = FRAME_LINE_HEIGHT (f) * WINDOW_TOP_EDGE_LINE (win);
852         }
854       p.x += XINT (x); p.y += XINT (y);
856       XSETFRAME (Vmenu_updating_frame, f);
857     }
858   else
859     {      /* no position given */
860       /* FIXME: if called during dump, we need to stop precomputation of
861          key equivalents (see below) because the keydefs in ns-win.el have
862          not been loaded yet. */
863       if (noninteractive)
864         return Qnil;
865       Vmenu_updating_frame = Qnil;
866     }
868   /* now parse the lisp menus */
869   record_unwind_protect (unuse_menu_items, Qnil);
870   title = Qnil;
871   GCPRO1 (title);
873   /* Decode the menu items from what was specified.  */
875   keymap = get_keymap (menu, 0, 0);
876   if (CONSP (keymap))
877     {
878       /* We were given a keymap.  Extract menu info from the keymap.  */
879       Lisp_Object prompt;
881       /* Extract the detailed info to make one pane.  */
882       keymap_panes (&menu, 1, NILP (position));
884       /* Search for a string appearing directly as an element of the keymap.
885          That string is the title of the menu.  */
886       prompt = Fkeymap_prompt (keymap);
887       title = NILP (prompt) ? build_string ("Select") : prompt;
889       /* Make that be the pane title of the first pane.  */
890       if (!NILP (prompt) && menu_items_n_panes >= 0)
891         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt;
893       keymaps = 1;
894     }
895   else if (CONSP (menu) && KEYMAPP (XCAR (menu)))
896     {
897       /* We were given a list of keymaps.  */
898       int nmaps = XFASTINT (Flength (menu));
899       Lisp_Object *maps
900         = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object));
901       int i;
903       title = Qnil;
905       /* The first keymap that has a prompt string
906          supplies the menu title.  */
907       for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem))
908         {
909           Lisp_Object prompt;
911           maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0);
913           prompt = Fkeymap_prompt (keymap);
914           if (NILP (title) && !NILP (prompt))
915             title = prompt;
916         }
918       /* Extract the detailed info to make one pane.  */
919       keymap_panes (maps, nmaps, NILP (position));
921       /* Make the title be the pane title of the first pane.  */
922       if (!NILP (title) && menu_items_n_panes >= 0)
923         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title;
925       keymaps = 1;
926     }
927   else
928     {
929       /* We were given an old-fashioned menu.  */
930       title = Fcar (menu);
931       CHECK_STRING (title);
933       list_of_panes (Fcdr (menu));
935       keymaps = 0;
936     }
938   unbind_to (specpdl_count, Qnil);
940   /* If no position given, that was a signal to just precompute and cache
941      key equivalents, which was a side-effect of what we just did. */
942   if (NILP (position))
943     {
944       discard_menu_items ();
945       UNGCPRO;
946       return Qnil;
947     }
949   record_unwind_protect (cleanup_popup_menu, Qnil);
950   BLOCK_INPUT;
952   /* now parse stage 2 as in ns_update_menubar */
953   wv = xmalloc_widget_value ();
954   wv->name = "contextmenu";
955   wv->value = 0;
956   wv->enabled = 1;
957   wv->button_type = BUTTON_TYPE_NONE;
958   wv->help = Qnil;
959   first_wv = wv;
961   specpdl_count2 = SPECPDL_INDEX ();
963 #if 0
964   /* FIXME: a couple of one-line differences prevent reuse */
965   wv = digest_single_submenu (0, menu_items_used, Qnil);
966 #else
967   {
968   widget_value *save_wv = 0, *prev_wv = 0;
969   widget_value **submenu_stack
970     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
971 /*   Lisp_Object *subprefix_stack
972        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
973   int submenu_depth = 0;
974   int first_pane = 1;
975   int i;
977   /* Loop over all panes and items, filling in the tree.  */
978   i = 0;
979   while (i < menu_items_used)
980     {
981       if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
982         {
983           submenu_stack[submenu_depth++] = save_wv;
984           save_wv = prev_wv;
985           prev_wv = 0;
986           first_pane = 1;
987           i++;
988         }
989       else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
990         {
991           prev_wv = save_wv;
992           save_wv = submenu_stack[--submenu_depth];
993           first_pane = 0;
994           i++;
995         }
996       else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
997                && submenu_depth != 0)
998         i += MENU_ITEMS_PANE_LENGTH;
999       /* Ignore a nil in the item list.
1000          It's meaningful only for dialog boxes.  */
1001       else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
1002         i += 1;
1003       else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
1004         {
1005           /* Create a new pane.  */
1006           Lisp_Object pane_name, prefix;
1007           char *pane_string;
1009           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
1010           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
1012 #ifndef HAVE_MULTILINGUAL_MENU
1013           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
1014             {
1015               pane_name = ENCODE_MENU_STRING (pane_name);
1016               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
1017             }
1018 #endif
1019           pane_string = (NILP (pane_name)
1020                          ? "" : (char *) SDATA (pane_name));
1021           /* If there is just one top-level pane, put all its items directly
1022              under the top-level menu.  */
1023           if (menu_items_n_panes == 1)
1024             pane_string = "";
1026           /* If the pane has a meaningful name,
1027              make the pane a top-level menu item
1028              with its items as a submenu beneath it.  */
1029           if (!keymaps && strcmp (pane_string, ""))
1030             {
1031               wv = xmalloc_widget_value ();
1032               if (save_wv)
1033                 save_wv->next = wv;
1034               else
1035                 first_wv->contents = wv;
1036               wv->name = pane_string;
1037               if (keymaps && !NILP (prefix))
1038                 wv->name++;
1039               wv->value = 0;
1040               wv->enabled = 1;
1041               wv->button_type = BUTTON_TYPE_NONE;
1042               wv->help = Qnil;
1043               save_wv = wv;
1044               prev_wv = 0;
1045             }
1046           else if (first_pane)
1047             {
1048               save_wv = wv;
1049               prev_wv = 0;
1050             }
1051           first_pane = 0;
1052           i += MENU_ITEMS_PANE_LENGTH;
1053         }
1054       else
1055         {
1056           /* Create a new item within current pane.  */
1057           Lisp_Object item_name, enable, descrip, def, type, selected, help;
1058           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
1059           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
1060           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
1061           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
1062           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
1063           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
1064           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
1066 #ifndef HAVE_MULTILINGUAL_MENU
1067           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
1068             {
1069               item_name = ENCODE_MENU_STRING (item_name);
1070               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
1071             }
1073           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
1074             {
1075               descrip = ENCODE_MENU_STRING (descrip);
1076               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
1077             }
1078 #endif /* not HAVE_MULTILINGUAL_MENU */
1080           wv = xmalloc_widget_value ();
1081           if (prev_wv)
1082             prev_wv->next = wv;
1083           else
1084             save_wv->contents = wv;
1085           wv->name = (char *) SDATA (item_name);
1086           if (!NILP (descrip))
1087             wv->key = (char *) SDATA (descrip);
1088           wv->value = 0;
1089           /* If this item has a null value,
1090              make the call_data null so that it won't display a box
1091              when the mouse is on it.  */
1092           wv->call_data
1093               = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
1094           wv->enabled = !NILP (enable);
1096           if (NILP (type))
1097             wv->button_type = BUTTON_TYPE_NONE;
1098           else if (EQ (type, QCtoggle))
1099             wv->button_type = BUTTON_TYPE_TOGGLE;
1100           else if (EQ (type, QCradio))
1101             wv->button_type = BUTTON_TYPE_RADIO;
1102           else
1103             abort ();
1105           wv->selected = !NILP (selected);
1107           if (! STRINGP (help))
1108             help = Qnil;
1110           wv->help = help;
1112           prev_wv = wv;
1114           i += MENU_ITEMS_ITEM_LENGTH;
1115         }
1116     }
1117   }
1118 #endif
1120   if (!NILP (title))
1121     {
1122       widget_value *wv_title = xmalloc_widget_value ();
1123       widget_value *wv_sep = xmalloc_widget_value ();
1125       /* Maybe replace this separator with a bitmap or owner-draw item
1126          so that it looks better.  Having two separators looks odd.  */
1127       wv_sep->name = "--";
1128       wv_sep->next = first_wv->contents;
1129       wv_sep->help = Qnil;
1131 #ifndef HAVE_MULTILINGUAL_MENU
1132       if (STRING_MULTIBYTE (title))
1133         title = ENCODE_MENU_STRING (title);
1134 #endif
1136       wv_title->name = (char *) SDATA (title);
1137       wv_title->enabled = NO;
1138       wv_title->button_type = BUTTON_TYPE_NONE;
1139       wv_title->help = Qnil;
1140       wv_title->next = wv_sep;
1141       first_wv->contents = wv_title;
1142     }
1144   pmenu = [[EmacsMenu alloc] initWithTitle:
1145                                [NSString stringWithUTF8String: SDATA (title)]];
1146   [pmenu fillWithWidgetValue: first_wv->contents];
1147   free_menubar_widget_value_tree (first_wv);
1148   unbind_to (specpdl_count2, Qnil);
1150   popup_activated_flag = 1;
1151   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1152   popup_activated_flag = 0;
1153   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1155   UNBLOCK_INPUT;
1156   discard_menu_items ();
1157   unbind_to (specpdl_count, Qnil);
1158   UNGCPRO;
1160   if (error_name) error (error_name);
1161   return tem;
1167 /* ==========================================================================
1169     Toolbar: externally-called functions
1171    ========================================================================== */
1173 void
1174 free_frame_tool_bar (FRAME_PTR f)
1175 /* --------------------------------------------------------------------------
1176     Under NS we just hide the toolbar until it might be needed again.
1177    -------------------------------------------------------------------------- */
1179   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1182 void
1183 update_frame_tool_bar (FRAME_PTR f)
1184 /* --------------------------------------------------------------------------
1185     Update toolbar contents
1186    -------------------------------------------------------------------------- */
1188   int i;
1189   EmacsToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar];
1191   [toolbar clearActive];
1193   /* update EmacsToolbar as in GtkUtils, build items list */
1194   for (i = 0; i < f->n_tool_bar_items; ++i)
1195     {
1196 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1197                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1199       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1200       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1201       int idx;
1202       int img_id;
1203       struct image *img;
1204       Lisp_Object image;
1205       Lisp_Object helpObj;
1206       char *helpText;
1208       /* If image is a vector, choose the image according to the
1209          button state.  */
1210       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1211       if (VECTORP (image))
1212         {
1213           /* NS toolbar auto-computes disabled and selected images */
1214           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1215           xassert (ASIZE (image) >= idx);
1216           image = AREF (image, idx);
1217         }
1218       else
1219         {
1220           idx = -1;
1221         }
1222       /* Ignore invalid image specifications.  */
1223       if (!valid_image_p (image))
1224         {
1225           NSLog (@"Invalid image for toolbar item");
1226           continue;
1227         }
1229       img_id = lookup_image (f, image);
1230       img = IMAGE_FROM_ID (f, img_id);
1231       prepare_image_for_display (f, img);
1233       if (img->load_failed_p || img->pixmap == nil)
1234         {
1235           NSLog (@"Could not prepare toolbar image for display.");
1236           continue;
1237         }
1239       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1240       if (NILP (helpObj))
1241         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1242       helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1244       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1245                                enabled: enabled_p];
1246 #undef TOOLPROP
1247     }
1249   if (![toolbar isVisible])
1250       [toolbar setVisible: YES];
1252   if ([toolbar changed])
1253     {
1254       /* inform app that toolbar has changed */
1255       NSDictionary *dict = [toolbar configurationDictionary];
1256       NSMutableDictionary *newDict = [dict mutableCopy];
1257       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1258       NSObject *key;
1259       while ((key = [keys nextObject]) != nil)
1260         {
1261           NSObject *val = [dict objectForKey: key];
1262           if ([val isKindOfClass: [NSArray class]])
1263             {
1264               [newDict setObject:
1265                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1266                           forKey: key];
1267               break;
1268             }
1269         }
1270       [toolbar setConfigurationFromDictionary: newDict];
1271       [newDict release];
1272     }
1277 /* ==========================================================================
1279     Toolbar: class implementation
1281    ========================================================================== */
1283 @implementation EmacsToolbar
1285 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1287   self = [super initWithIdentifier: identifier];
1288   emacsView = view;
1289   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1290   [self setSizeMode: NSToolbarSizeModeSmall];
1291   [self setDelegate: self];
1292   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1293   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1294   prevEnablement = enablement = 0L;
1295   return self;
1298 - (void)dealloc
1300   [prevIdentifiers release];
1301   [activeIdentifiers release];
1302   [identifierToItem release];
1303   [super dealloc];
1306 - (void) clearActive
1308   [prevIdentifiers release];
1309   prevIdentifiers = [activeIdentifiers copy];
1310   [activeIdentifiers removeAllObjects];
1311   prevEnablement = enablement;
1312   enablement = 0L;
1315 - (BOOL) changed
1317   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1318     enablement == prevEnablement ? NO : YES;
1321 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1322                         helpText: (char *)help enabled: (BOOL)enabled
1324   /* 1) come up w/identifier */
1325   NSString *identifier
1326       = [NSString stringWithFormat: @"%u", [img hash]];
1328   /* 2) create / reuse item */
1329   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1330   if (item == nil)
1331     {
1332       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1333                autorelease];
1334       [item setImage: img];
1335       [item setToolTip: [NSString stringWithCString: help]];
1336       [item setTarget: emacsView];
1337       [item setAction: @selector (toolbarClicked:)];
1338     }
1340   [item setTag: idx];
1341   [item setEnabled: enabled];
1343   /* 3) update state */
1344   [identifierToItem setObject: item forKey: identifier];
1345   [activeIdentifiers addObject: identifier];
1346   enablement = (enablement << 1) | (enabled == YES);
1349 /* This overrides super's implementation, which automatically sets
1350    all items to enabled state (for some reason). */
1351 - (void)validateVisibleItems { }
1354 /* delegate methods */
1356 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1357       itemForItemIdentifier: (NSString *)itemIdentifier
1358   willBeInsertedIntoToolbar: (BOOL)flag
1360   /* look up NSToolbarItem by identifier and return... */
1361   return [identifierToItem objectForKey: itemIdentifier];
1364 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1366   /* return entire set.. */
1367   return activeIdentifiers;
1370 /* for configuration palette (not yet supported) */
1371 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1373   /* return entire set... */
1374   return [identifierToItem allKeys];
1377 /* optional and unneeded */
1378 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1379 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1380 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1382 @end  /* EmacsToolbar */
1386 /* ==========================================================================
1388     Tooltip: class implementation
1390    ========================================================================== */
1392 /* Needed because NeXTstep does not provide enough control over tooltip
1393    display. */
1394 @implementation EmacsTooltip
1396 - init
1398   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1399                                             blue: 0.792 alpha: 0.95];
1400   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1401   NSFont *sfont = [font screenFont];
1402   int height = [sfont ascender] - [sfont descender];
1403 /*[font boundingRectForFont].size.height; */
1404   NSRect r = NSMakeRect (0, 0, 100, height+6);
1406   textField = [[NSTextField alloc] initWithFrame: r];
1407   [textField setFont: font];
1408   [textField setBackgroundColor: col];
1410   [textField setEditable: NO];
1411   [textField setSelectable: NO];
1412   [textField setBordered: YES];
1413   [textField setBezeled: YES];
1414   [textField setDrawsBackground: YES];
1416   win = [[NSWindow alloc]
1417             initWithContentRect: [textField frame]
1418                       styleMask: 0
1419                         backing: NSBackingStoreBuffered
1420                           defer: YES];
1421   [win setReleasedWhenClosed: NO];
1422   [win setDelegate: self];
1423   [[win contentView] addSubview: textField];
1424 /*  [win setBackgroundColor: col]; */
1425   [win setOpaque: NO];
1427   return self;
1430 - (void) dealloc
1432   [win close];
1433   [win release];
1434   [textField release];
1435   [super dealloc];
1438 - (void) setText: (char *)text
1440   NSString *str = [NSString stringWithUTF8String: text];
1441   NSRect r = [textField frame];
1442   r.size.width = [[[textField font] screenFont] widthOfString: str] + 8;
1443   [textField setFrame: r];
1444   [textField setStringValue: str];
1447 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1449   NSRect wr = [win frame];
1451   wr.origin = NSMakePoint (x, y);
1452   wr.size = [textField frame].size;
1454   [win setFrame: wr display: YES];
1455   [win orderFront: self];
1456   [win display];
1457   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1458                                          selector: @selector (hide)
1459                                          userInfo: nil repeats: NO];
1460   [timer retain];
1463 - (void) hide
1465   [win close];
1466   if (timer != nil)
1467     {
1468       if ([timer isValid])
1469         [timer invalidate];
1470       [timer release];
1471       timer = nil;
1472     }
1475 - (BOOL) isActive
1477   return timer != nil;
1480 - (NSRect) frame
1482   return [textField frame];
1485 @end  /* EmacsTooltip */
1489 /* ==========================================================================
1491     Popup Dialog: implementing functions
1493    ========================================================================== */
1496 static Lisp_Object
1497 pop_down_menu (Lisp_Object arg)
1499   struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1500   if (popup_activated_flag)
1501     {
1502       popup_activated_flag = 0;
1503       BLOCK_INPUT;
1504       [NSApp endModalSession: popupSession];
1505       [((EmacsDialogPanel *) (p->pointer)) close];
1506       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1507       UNBLOCK_INPUT;
1508     }
1509   return Qnil;
1513 Lisp_Object
1514 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1516   id dialog;
1517   Lisp_Object window, tem;
1518   struct frame *f;
1519   NSPoint p;
1520   BOOL isQ;
1522   NSTRACE (x-popup-dialog);
1523   
1524   check_ns ();
1526   isQ = NILP (header);
1528   if (EQ (position, Qt)
1529       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1530                                || EQ (XCAR (position), Qtool_bar))))
1531     {
1532       window = selected_window;
1533     }
1534   else if (CONSP (position))
1535     {
1536       Lisp_Object tem;
1537       tem = Fcar (position);
1538       if (XTYPE (tem) == Lisp_Cons)
1539         window = Fcar (Fcdr (position));
1540       else
1541         {
1542           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1543           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1544         }
1545     }
1546   else if (WINDOWP (position) || FRAMEP (position))
1547     {
1548       window = position;
1549     }
1550   else
1551     window = Qnil;
1553   if (FRAMEP (window))
1554     f = XFRAME (window);
1555   else if (WINDOWP (window))
1556     {
1557       CHECK_LIVE_WINDOW (window);
1558       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1559     }
1560   else
1561     CHECK_WINDOW (window);
1563   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1564   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1566   BLOCK_INPUT;
1567   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1568                                            isQuestion: isQ];
1569   {
1570     int specpdl_count = SPECPDL_INDEX ();
1571     record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1572     popup_activated_flag = 1;
1573     tem = [dialog runDialogAt: p];
1574     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1575   }
1576   UNBLOCK_INPUT;
1578   return tem;
1582 /* ==========================================================================
1584     Popup Dialog: class implementation
1586    ========================================================================== */
1588 @interface FlippedView : NSView
1591 @end
1593 @implementation FlippedView
1594 - (BOOL)isFlipped
1596   return YES;
1598 @end
1600 @implementation EmacsDialogPanel
1602 #define SPACER          8.0
1603 #define ICONSIZE        64.0
1604 #define TEXTHEIGHT      20.0
1605 #define MINCELLWIDTH    90.0
1607 - initWithContentRect: (NSRect)contentRect styleMask: (unsigned int)aStyle
1608               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1610   NSSize spacing = {SPACER, SPACER};
1611   NSRect area;
1612   char this_cmd_name[80];
1613   id cell;
1614   static NSImageView *imgView;
1615   static FlippedView *contentView;
1617   if (imgView == nil)
1618     {
1619       NSImage *img;
1620       area.origin.x   = 3*SPACER;
1621       area.origin.y   = 2*SPACER;
1622       area.size.width = ICONSIZE;
1623       area.size.height= ICONSIZE;
1624       img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1625       [img setScalesWhenResized: YES];
1626       [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1627       imgView = [[NSImageView alloc] initWithFrame: area];
1628       [imgView setImage: img];
1629       [imgView setEditable: NO];
1630       [img release];
1631     }
1633   aStyle = NSTitledWindowMask;
1634   flag = YES;
1635   rows = 0;
1636   cols = 1;
1637   [super initWithContentRect: contentRect styleMask: aStyle
1638                      backing: backingType defer: flag];
1639   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1640   [self setContentView: contentView];
1642   [[self contentView] setAutoresizesSubviews: YES];
1644   [[self contentView] addSubview: imgView];
1645   [self setTitle: @""];
1647   area.origin.x   += ICONSIZE+2*SPACER;
1648 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1649   area.size.width = 400;
1650   area.size.height= TEXTHEIGHT;
1651   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1652   [[self contentView] addSubview: command];
1653   [command setStringValue: @"Emacs"];
1654   [command setDrawsBackground: NO];
1655   [command setBezeled: NO];
1656   [command setSelectable: NO];
1657   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1659 /*  area.origin.x   = ICONSIZE+2*SPACER;
1660   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1661   area.size.width = 400;
1662   area.size.height= 2;
1663   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1664   [[self contentView] addSubview: tem];
1665   [tem setTitlePosition: NSNoTitle];
1666   [tem setAutoresizingMask: NSViewWidthSizable];*/
1668 /*  area.origin.x = ICONSIZE+2*SPACER; */
1669   area.origin.y += TEXTHEIGHT+SPACER;
1670   area.size.width = 400;
1671   area.size.height= TEXTHEIGHT;
1672   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1673   [[self contentView] addSubview: title];
1674   [title setDrawsBackground: NO];
1675   [title setBezeled: NO];
1676   [title setSelectable: NO];
1677   [title setFont: [NSFont systemFontOfSize: 11.0]];
1679   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1680   [cell setBordered: NO];
1681   [cell setEnabled: NO];
1682   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1683   [cell setBezelStyle: NSRoundedBezelStyle];
1685   matrix = [[NSMatrix alloc] initWithFrame: contentRect 
1686                                       mode: NSHighlightModeMatrix 
1687                                  prototype: cell 
1688                               numberOfRows: 0 
1689                            numberOfColumns: 1];
1690   [[self contentView] addSubview: matrix];
1691   [matrix release];
1692   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1693                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1694   [matrix setIntercellSpacing: spacing];
1696   [self setOneShot: YES];
1697   [self setReleasedWhenClosed: YES];
1698   [self setHidesOnDeactivate: YES];
1699   return self;
1703 - (BOOL)windowShouldClose: (id)sender
1705   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1706   return NO;
1710 void process_dialog (id window, Lisp_Object list)
1712   Lisp_Object item;
1713   int row = 0;
1715   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1716     {
1717       item = XCAR (list);
1718       if (XTYPE (item) == Lisp_String)
1719         {
1720           [window addString: SDATA (item) row: row++];
1721         }
1722       else if (XTYPE (item) == Lisp_Cons)
1723         {
1724           [window addButton: SDATA (XCAR (item))
1725                       value: XCDR (item) row: row++];
1726         }
1727       else if (NILP (item))
1728         {
1729           [window addSplit];
1730           row = 0;
1731         }
1732     }
1736 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1738   id cell;
1739        
1740   if (row >= rows)
1741     {
1742       [matrix addRow];
1743       rows++;
1744     }
1745   cell = [matrix cellAtRow: row column: cols-1];
1746   [cell setTarget: self];
1747   [cell setAction: @selector (clicked: )];
1748   [cell setTitle: [NSString stringWithUTF8String: str]];
1749   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1750   [cell setBordered: YES];
1751   [cell setEnabled: YES];
1753   return self;
1757 - addString: (char *)str row: (int)row
1759   id cell;
1760        
1761   if (row >= rows)
1762     {
1763       [matrix addRow];
1764       rows++;
1765     }
1766   cell = [matrix cellAtRow: row column: cols-1];
1767   [cell setTitle: [NSString stringWithUTF8String: str]];
1768   [cell setBordered: YES];
1769   [cell setEnabled: NO];
1771   return self;
1775 - addSplit
1777   [matrix addColumn];
1778   cols++;
1779   return self;
1783 - clicked: sender
1785   NSArray *sellist = nil;
1786   EMACS_INT seltag;
1788   sellist = [sender selectedCells];
1789   if ([sellist count]<1) 
1790     return self;
1792   seltag = [[sellist objectAtIndex: 0] tag];
1793   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1794     [NSApp stopModalWithCode: seltag];
1795   return self;
1799 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1801   Lisp_Object head;
1802   [super init];
1804   if (XTYPE (contents) == Lisp_Cons)
1805     {
1806       head = Fcar (contents);
1807       process_dialog (self, Fcdr (contents));
1808     }
1809   else
1810     head = contents;
1812   if (XTYPE (head) == Lisp_String)
1813       [title setStringValue:
1814                  [NSString stringWithUTF8String: SDATA (head)]];
1815   else if (isQ == YES)
1816       [title setStringValue: @"Question"];
1817   else
1818       [title setStringValue: @"Information"];
1820   {
1821     int i;
1822     NSRect r, s, t;
1824     if (cols == 1 && rows > 1)  /* Never told where to split */
1825       {
1826         [matrix addColumn];
1827         for (i = 0; i<rows/2; i++)
1828           {
1829             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1830                       atRow: i column: 1];
1831             [matrix removeRow: (rows+1)/2];
1832           }
1833       }
1835     [matrix sizeToFit];
1836     {
1837       NSSize csize = [matrix cellSize];
1838       if (csize.width < MINCELLWIDTH)
1839         {
1840           csize.width = MINCELLWIDTH;
1841           [matrix setCellSize: csize];
1842           [matrix sizeToCells];
1843         }
1844     }
1846     [title sizeToFit];
1847     [command sizeToFit];
1849     t = [matrix frame];
1850     r = [title frame];
1851     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1852       {
1853         t.origin.x   = r.origin.x;
1854         t.size.width = r.size.width;
1855       }
1856     r = [command frame];
1857     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1858       {
1859         t.origin.x   = r.origin.x;
1860         t.size.width = r.size.width;
1861       }
1863     r = [self frame];
1864     s = [(NSView *)[self contentView] frame];
1865     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1866     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1867     [self setFrame: r display: NO];
1868   }
1870   return self;
1874 - (void)dealloc
1876   { [super dealloc]; return; };
1880 - (Lisp_Object)runDialogAt: (NSPoint)p
1882   int ret;
1884   /* initiate a session that will be ended by pop_down_menu */
1885   popupSession = [NSApp beginModalSessionForWindow: self];
1886   while (popup_activated_flag
1887          && (ret = [NSApp runModalSession: popupSession])
1888               == NSRunContinuesResponse)
1889     {
1890       /* Run this for timers.el, indep of atimers; might not return.
1891          TODO: use return value to avoid calling every iteration. */
1892       timer_check (1);
1893       [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1894     }
1896   {                             /* FIXME: BIG UGLY HACK!!! */
1897       Lisp_Object tmp;
1898       *(EMACS_INT*)(&tmp) = ret;
1899       return tmp;
1900   }
1903 @end
1906 /* ==========================================================================
1908     Lisp definitions
1910    ========================================================================== */
1912 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1913        doc: /* Cause the NS menu to be re-calculated.  */)
1914      ()
1916   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1917   return Qnil;
1921 DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
1922        doc: /* Pop up a deck-of-cards menu and return user's selection.
1923 POSITION is a position specification.  This is either a mouse button event
1924 or a list ((XOFFSET YOFFSET) WINDOW)
1925 where XOFFSET and YOFFSET are positions in pixels from the top left
1926 corner of WINDOW.  (WINDOW may be a window or a frame object.)
1927 This controls the position of the top left of the menu as a whole.
1928 If POSITION is t, it means to use the current mouse position.
1930 MENU is a specifier for a menu.  For the simplest case, MENU is a keymap.
1931 The menu items come from key bindings that have a menu string as well as
1932 a definition; actually, the \"definition\" in such a key binding looks like
1933 \(STRING . REAL-DEFINITION).  To give the menu a title, put a string into
1934 the keymap as a top-level element.
1936 If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.
1937 Otherwise, REAL-DEFINITION should be a valid key binding definition.
1939 You can also use a list of keymaps as MENU.
1940   Then each keymap makes a separate pane.
1942 When MENU is a keymap or a list of keymaps, the return value is the
1943 list of events corresponding to the user's choice. Note that
1944 `x-popup-menu' does not actually execute the command bound to that
1945 sequence of events.
1947 Alternatively, you can specify a menu of multiple panes
1948   with a list of the form (TITLE PANE1 PANE2...),
1949 where each pane is a list of form (TITLE ITEM1 ITEM2...).
1950 Each ITEM is normally a cons cell (STRING . VALUE);
1951 but a string can appear as an item--that makes a nonselectable line
1952 in the menu.
1953 With this form of menu, the return value is VALUE from the chosen item.  */)
1954      (position, menu)
1955      Lisp_Object position, menu;
1957   return ns_popup_menu (position, menu);
1961 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1962        doc: /* Pop up a dialog box and return user's selection.
1963 POSITION specifies which frame to use.
1964 This is normally a mouse button event or a window or frame.
1965 If POSITION is t, it means to use the frame the mouse is on.
1966 The dialog box appears in the middle of the specified frame.
1968 CONTENTS specifies the alternatives to display in the dialog box.
1969 It is a list of the form (DIALOG ITEM1 ITEM2...).
1970 Each ITEM is a cons cell (STRING . VALUE).
1971 The return value is VALUE from the chosen item.
1973 An ITEM may also be just a string--that makes a nonselectable item.
1974 An ITEM may also be nil--that means to put all preceding items
1975 on the left of the dialog box and all following items on the right.
1976 \(By default, approximately half appear on each side.)
1978 If HEADER is non-nil, the frame title for the box is "Information",
1979 otherwise it is "Question".
1981 If the user gets rid of the dialog box without making a valid choice,
1982 for instance using the window manager, then this produces a quit and
1983 `x-popup-dialog' does not return.  */)
1984      (position, contents, header)
1985      Lisp_Object position, contents, header;
1987   return ns_popup_dialog (position, contents, header);
1990 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1991        doc: /* Return t if a menu or popup dialog is active.  */)
1992      ()
1994   return popup_activated () ? Qt : Qnil;
1997 /* ==========================================================================
1999     Lisp interface declaration
2001    ========================================================================== */
2003 void
2004 syms_of_nsmenu ()
2006   defsubr (&Sx_popup_menu);
2007   defsubr (&Sx_popup_dialog);
2008   defsubr (&Sns_reset_menu);
2009   defsubr (&Smenu_or_popup_active_p);
2010   staticpro (&menu_items);
2011   menu_items = Qnil;
2013   Qdebug_on_next_call = intern ("debug-on-next-call");
2014   staticpro (&Qdebug_on_next_call);
2017 // arch-tag: 75773656-52e5-4c44-a398-47bd87b32619