(Old Revisions): Fix diff-switches description.
[emacs.git] / src / nsmenu.m
blob8b06bdaddf89282447a36ca85d0f7e1c9796ee08
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007, 2008 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 /* for profiling */
40 #include <sys/timeb.h>
41 #include <sys/types.h>
43 #define MenuStagger 10.0
45 #if 0
46 int menu_trace_num = 0;
47 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
48                                 __FILE__, __LINE__, ++menu_trace_num)
49 #else
50 #define NSTRACE(x)
51 #endif
53 #if 0
54 /* Include lisp -> C common menu parsing code */
55 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
56 #include "nsmenu_common.c"
57 #endif
59 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
60 extern Lisp_Object QCtoggle, QCradio;
62 extern Lisp_Object Vmenu_updating_frame;
64 Lisp_Object Qdebug_on_next_call;
65 extern Lisp_Object Voverriding_local_map, Voverriding_local_map_menu_flag,
66                    Qoverriding_local_map, Qoverriding_terminal_local_map;
68 extern long context_menu_value;
69 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
71 /* Nonzero means a menu is currently active.  */
72 static int popup_activated_flag;
74 /* NOTE: toolbar implementation is at end,
75   following complete menu implementation. */
78 /* ==========================================================================
80     Menu: Externally-called functions
82    ========================================================================== */
85 /*23: FIXME: not currently used, but should normalize with other terms. */
86 void
87 x_activate_menubar (struct frame *f)
89     fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
93 /* Supposed to discard menubar and free storage.  Since we share the
94    menubar among frames and update its context for the focused window,
95    there is nothing to do here. */
96 void
97 free_frame_menubar (struct frame *f)
99   return;
104 popup_activated ()
106   return popup_activated_flag;
110 /* --------------------------------------------------------------------------
111     Update menubar.  Three cases:
112     1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
113        just top-level menu strings (OS X), or goto case (2) (GNUstep).
114     2) deep_p = 1, submenu = nil: Recompute all submenus.
115     3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
116    -------------------------------------------------------------------------- */
117 /*#define NSMENUPROFILE 1 */
118 void
119 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
121   NSAutoreleasePool *pool;
122   id menu = [NSApp mainMenu];
123   static EmacsMenu *last_submenu = nil;
124   BOOL needsSet = NO;
125   const char *submenuTitle = [[submenu title] UTF8String];
126   extern int waiting_for_input;
127   int owfi;
128   Lisp_Object items;
129   widget_value *wv, *first_wv, *prev_wv = 0;
130   int i;
132 #ifdef NSMENUPROFILE
133   struct timeb tb;
134   long t;
135 #endif
137   NSTRACE (set_frame_menubar);
139   if (f != SELECTED_FRAME ())
140       return;
141   XSETFRAME (Vmenu_updating_frame, f);
142 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
144   BLOCK_INPUT;
145   pool = [[NSAutoreleasePool alloc] init];
147   /* Menu may have been created automatically; if so, discard it. */
148   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
149     {
150       [menu release];
151       menu = nil;
152     }
154   if (menu == nil)
155     {
156       menu = [[EmacsMenu alloc] initWithTitle: @"Emacs"];
157       needsSet = YES;
158     }
159   else
160     {  /* close up anything on there */
161       id attMenu = [menu attachedMenu];
162       if (attMenu != nil)
163         [attMenu close];
164     }
166 #ifdef NSMENUPROFILE
167   ftime (&tb);
168   t = -(1000*tb.time+tb.millitm);
169 #endif
171   /* widget_value is a straightforward object translation of emacs's
172      Byzantine lisp menu structures */
173   wv = xmalloc_widget_value ();
174   wv->name = "Emacs";
175   wv->value = 0;
176   wv->enabled = 1;
177   wv->button_type = BUTTON_TYPE_NONE;
178   wv->help = Qnil;
179   first_wv = wv;
181 #ifdef NS_IMPL_GNUSTEP
182   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
183 #endif
185   if (deep_p)
186     {
187       /* Fully parse one or more of the submenus. */
188       int n = 0;
189       int *submenu_start, *submenu_end;
190       int *submenu_top_level_items, *submenu_n_panes;
191       struct buffer *prev = current_buffer;
192       Lisp_Object buffer;
193       int specpdl_count = SPECPDL_INDEX ();
194       int previous_menu_items_used = f->menu_bar_items_used;
195       Lisp_Object *previous_items
196         = (Lisp_Object *) alloca (previous_menu_items_used
197                                   * sizeof (Lisp_Object));
199       /* lisp preliminaries */
200       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
201       specbind (Qinhibit_quit, Qt);
202       specbind (Qdebug_on_next_call, Qnil);
203       record_unwind_save_match_data ();
204       if (NILP (Voverriding_local_map_menu_flag))
205         {
206           specbind (Qoverriding_terminal_local_map, Qnil);
207           specbind (Qoverriding_local_map, Qnil);
208         }
209       set_buffer_internal_1 (XBUFFER (buffer));
211       /* TODO: for some reason this is not needed in other terms,
212            but some menu updates call Info-extract-pointer which causes
213            abort-on-error if waiting-for-input.  Needs further investigation. */
214       owfi = waiting_for_input;
215       waiting_for_input = 0;
217       /* lucid hook and possible reset */
218       safe_run_hooks (Qactivate_menubar_hook);
219       if (! NILP (Vlucid_menu_bar_dirty_flag))
220         call0 (Qrecompute_lucid_menubar);
221       safe_run_hooks (Qmenu_bar_update_hook);
222       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
224       /* Now ready to go */
225       items = FRAME_MENU_BAR_ITEMS (f);
227       /* Save the frame's previous menu bar contents data */
228       if (previous_menu_items_used)
229         bcopy (XVECTOR (f->menu_bar_vector)->contents, previous_items,
230                previous_menu_items_used * sizeof (Lisp_Object));
232       /* parse stage 1: extract from lisp */
233       save_menu_items ();
235       menu_items = f->menu_bar_vector;
236       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
237       submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
238       submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
239       submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
240       submenu_top_level_items
241         = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
242       init_menu_items ();
243       for (i = 0; i < XVECTOR (items)->size; i += 4)
244         {
245           Lisp_Object key, string, maps;
247           key = XVECTOR (items)->contents[i];
248           string = XVECTOR (items)->contents[i + 1];
249           maps = XVECTOR (items)->contents[i + 2];
250           if (NILP (string))
251             break;
253           /* FIXME: we'd like to only parse the needed submenu, but this
254                was causing crashes in the _common parsing code.. need to make
255                sure proper initialization done.. */
256 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
257              continue; */
259           submenu_start[i] = menu_items_used;
261           menu_items_n_panes = 0;
262           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
263           submenu_n_panes[i] = menu_items_n_panes;
264           submenu_end[i] = menu_items_used;
265           n++;
266         }
268       finish_menu_items ();
269       waiting_for_input = owfi;
272       if (submenu && n == 0)
273         {
274           /* should have found a menu for this one but didn't */
275           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
276                   submenuTitle);
277           discard_menu_items ();
278           unbind_to (specpdl_count, Qnil);
279           [pool release];
280           UNBLOCK_INPUT;
281           return;
282         }
284       /* parse stage 2: insert into lucid 'widget_value' structures
285          [comments in other terms say not to evaluate lisp code here] */
286       wv = xmalloc_widget_value ();
287       wv->name = "menubar";
288       wv->value = 0;
289       wv->enabled = 1;
290       wv->button_type = BUTTON_TYPE_NONE;
291       wv->help = Qnil;
292       first_wv = wv;
294       for (i = 0; i < 4*n; i += 4)
295         {
296           menu_items_n_panes = submenu_n_panes[i];
297           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
298                                       submenu_top_level_items[i]);
299           if (prev_wv)
300             prev_wv->next = wv;
301           else
302             first_wv->contents = wv;
303           /* Don't set wv->name here; GC during the loop might relocate it.  */
304           wv->enabled = 1;
305           wv->button_type = BUTTON_TYPE_NONE;
306           prev_wv = wv;
307         }
309       set_buffer_internal_1 (prev);
311       /* Compare the new menu items with previous, and leave off if no change */
312       /* FIXME: following other terms here, but seems like this should be
313            done before parse stage 2 above, since its results aren't used */
314       if (previous_menu_items_used
315           && (!submenu || (submenu && submenu == last_submenu))
316           && menu_items_used == previous_menu_items_used)
317         {
318           for (i = 0; i < previous_menu_items_used; i++)
319             /* FIXME: this ALWAYS fails on Buffers menu items.. something
320                  about their strings causes them to change every time, so we
321                  double-check failures */
322             if (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i]))
323               if (!(STRINGP (previous_items[i])
324                     && STRINGP (XVECTOR (menu_items)->contents[i])
325                     && !strcmp (SDATA (previous_items[i]),
326                                SDATA (XVECTOR (menu_items)->contents[i]))))
327                   break;
328           if (i == previous_menu_items_used)
329             {
330               /* No change.. */
332 #ifdef NSMENUPROFILE
333               ftime (&tb);
334               t += 1000*tb.time+tb.millitm;
335               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
336 #endif
338               free_menubar_widget_value_tree (first_wv);
339               discard_menu_items ();
340               unbind_to (specpdl_count, Qnil);
341               [pool release];
342               UNBLOCK_INPUT;
343               return;
344             }
345         }
346       /* The menu items are different, so store them in the frame */
347       /* FIXME: this is not correct for single-submenu case */
348       f->menu_bar_vector = menu_items;
349       f->menu_bar_items_used = menu_items_used;
351       /* Calls restore_menu_items, etc., as they were outside */
352       unbind_to (specpdl_count, Qnil);
354       /* Parse stage 2a: now GC cannot happen during the lifetime of the
355          widget_value, so it's safe to store data from a Lisp_String */
356       wv = first_wv->contents;
357       for (i = 0; i < XVECTOR (items)->size; i += 4)
358         {
359           Lisp_Object string;
360           string = XVECTOR (items)->contents[i + 1];
361           if (NILP (string))
362             break;
363 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
364                continue; */
366           wv->name = (char *) SDATA (string);
367           update_submenu_strings (wv->contents);
368           wv = wv->next;
369         }
371       /* Now, update the NS menu; if we have a submenu, use that, otherwise
372          create a new menu for each sub and fill it. */
373       if (submenu)
374         {
375           for (wv = first_wv->contents; wv; wv = wv->next)
376             {
377               if (!strcmp (submenuTitle, wv->name))
378                 {
379                   [submenu fillWithWidgetValue: wv->contents];
380                   last_submenu = submenu;
381                   break;
382                 }
383             }
384         }
385       else
386         {
387           [menu fillWithWidgetValue: first_wv->contents];
388         }
390     }
391   else
392     {
393       static int n_previous_strings = 0;
394       static char previous_strings[100][10];
395       static struct frame *last_f = NULL;
396       int n;
397       Lisp_Object string;
399       /* Make widget-value tree w/ just the top level menu bar strings */
400       items = FRAME_MENU_BAR_ITEMS (f);
401       if (NILP (items))
402         {
403           [pool release];
404           UNBLOCK_INPUT;
405           return;
406         }
409       /* check if no change.. this mechanism is a bit rough, but ready */
410       n = XVECTOR (items)->size / 4;
411       if (f == last_f && n_previous_strings == n)
412         {
413           for (i = 0; i<n; i++)
414             {
415               string = AREF (items, 4*i+1);
417               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
418                 continue;
419               if (NILP (string))
420                 if (previous_strings[i][0])
421                   break;
422               else
423                 continue;
424               if (strncmp (previous_strings[i], SDATA (string), 10))
425                 break;
426             }
428           if (i == n)
429             {
430               [pool release];
431               UNBLOCK_INPUT;
432               return;
433             }
434         }
436       [menu clear];
437       for (i = 0; i < XVECTOR (items)->size; i += 4)
438         {
439           string = XVECTOR (items)->contents[i + 1];
440           if (NILP (string))
441             break;
443           if (n < 100)
444             strncpy (previous_strings[i/4], SDATA (string), 10);
446           wv = xmalloc_widget_value ();
447           wv->name = (char *) SDATA (string);
448           wv->value = 0;
449           wv->enabled = 1;
450           wv->button_type = BUTTON_TYPE_NONE;
451           wv->help = Qnil;
452           wv->call_data = (void *) (EMACS_INT) (-1);
454 #ifdef NS_IMPL_COCOA
455           /* we'll update the real copy under app menu when time comes */
456           if (!strcmp ("Services", wv->name))
457             {
458               /* but we need to make sure it will update on demand */
459               [svcsMenu setFrame: f];
460               [svcsMenu setDelegate: svcsMenu];
461             }
462           else
463 #endif
464           [menu addSubmenuWithTitle: wv->name forFrame: f];
466           if (prev_wv)
467             prev_wv->next = wv;
468           else
469             first_wv->contents = wv;
470           prev_wv = wv;
471         }
473       last_f = f;
474       if (n < 100)
475         n_previous_strings = n;
476       else
477         n_previous_strings = 0;
479     }
480   free_menubar_widget_value_tree (first_wv);
483 #ifdef NSMENUPROFILE
484   ftime (&tb);
485   t += 1000*tb.time+tb.millitm;
486   fprintf (stderr, "Menu update took %ld msec.\n", t);
487 #endif
489   /* set main menu */
490   if (needsSet)
491     [NSApp setMainMenu: menu];
493   [pool release];
494   UNBLOCK_INPUT;
499 /* Main emacs core entry point for menubar menus: called to indicate that the
500    frame's menus have changed, and the *step representation should be updated
501    from Lisp. */
502 void
503 set_frame_menubar (struct frame *f, int first_time, int deep_p)
505   ns_update_menubar (f, deep_p, nil);
509 /* Utility (from macmenu.c): is this item a separator? */
510 static int
511 name_is_separator (name)
512      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 = [[FRAME_NS_VIEW (frame) window] currentEvent];
569   /* HACK: Cocoa/Carbon will request update on every keystroke
570      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
571      since key equivalents are handled through emacs.
572      On Leopard, even keystroke events generate SystemDefined events, but
573      their subtype is 8. */
574   if ([event type] != NSSystemDefined || [event subtype] == 8)
575     return;
576 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
577   ns_update_menubar (frame, 1, self);
581 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
583   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
584       && FRAME_NS_VIEW (SELECTED_FRAME ()))
585     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
586   return YES;
590 /* parse a wdiget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
591    into an accelerator string */
592 -(NSString *)parseKeyEquiv: (char *)key
594   char *tpos = key;
595   keyEquivModMask = 0;
596   /* currently we just parse 'super' combinations;
597      later we'll set keyEquivModMask */
598   if (!key || !strlen (key))
599     return @"";
600   
601   while (*tpos == ' ' || *tpos == '(')
602     tpos++;
603   if (*tpos != 's'/* || tpos[3] != ')'*/)
604     return @"";
605   return [NSString stringWithFormat: @"%c", tpos[2]];
609 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
611   NSMenuItem *item;
612   widget_value *wv = (widget_value *)wvptr;
614   if (name_is_separator (wv->name))
615     {
616       item = [NSMenuItem separatorItem];
617       [self addItem: item];
618     }
619   else
620     {
621       NSString *title, *keyEq;
622       title = [NSString stringWithUTF8String: wv->name];
623       if (title == nil)
624         title = @"< ? >";  /* (get out in the open so we know about it) */
626       keyEq = [self parseKeyEquiv: wv->key];
628       item = [self addItemWithTitle: (NSString *)title
629                              action: @selector (menuDown:)
630                       keyEquivalent: keyEq];
631       if (keyEquivModMask)
632         [item setKeyEquivalentModifierMask: keyEquivModMask];
634       [item setEnabled: wv->enabled];
636       /* Draw radio buttons and tickboxes */
637       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
638                            wv->button_type == BUTTON_TYPE_RADIO))
639         [item setState: NSOnState];
640       else
641         [item setState: NSOffState];
643       [item setTag: (int)wv->call_data];
644     }
646   return item;
650 /* convenience */
651 -(void) clear
653   int n;
654   
655   for (n = [self numberOfItems]-1; n >= 0; n--)
656     {
657       NSMenuItem *item = [self itemAtIndex: n];
658       NSString *title = [item title];
659       if (([title length] == 0 || [@"Apple" isEqualToString: title])
660           && ![item isSeparatorItem])
661         continue;
662       [self removeItemAtIndex: n];
663     }
667 - (void)fillWithWidgetValue: (void *)wvptr
669   widget_value *wv = (widget_value *)wvptr;
671   /* clear existing contents */
672   [self setMenuChangedMessagesEnabled: NO];
673   [self clear];
675   /* add new contents */
676   for (; wv != NULL; wv = wv->next)
677     {
678       NSMenuItem *item = [self addItemWithWidgetValue: wv];
680       if (wv->contents)
681         {
682           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: @"Submenu"];
684           [self setSubmenu: submenu forItem: item];
685           [submenu fillWithWidgetValue: wv->contents];
686           [submenu release];
687           [item setAction: nil];
688         }
689     }
691   [self setMenuChangedMessagesEnabled: YES];
692 #ifdef NS_IMPL_GNUSTEP
693   if ([[self window] isVisible])
694     [self sizeToFit];
695 #else
696   if ([self supermenu] == nil)
697     [self sizeToFit];
698 #endif
702 /* adds an empty submenu and returns it */
703 - (EmacsMenu *)addSubmenuWithTitle: (char *)title forFrame: (struct frame *)f
705   NSString *titleStr = [NSString stringWithUTF8String: title];
706   NSMenuItem *item = [self addItemWithTitle: titleStr
707                                      action: nil /*@selector (menuDown:) */
708                               keyEquivalent: @""];
709   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
710   [self setSubmenu: submenu forItem: item];
711   [submenu release];
712   return submenu;
715 /* run a menu in popup mode */
716 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
717                  keymaps: (int)keymaps
719   EmacsView *view = FRAME_NS_VIEW (f);
720 /*   p = [view convertPoint:p fromView: nil]; */
721   p.y = NSHeight ([view frame]) - p.y;
722   NSEvent *e = [[view window] currentEvent];
723   NSEvent *event = [NSEvent mouseEventWithType: NSRightMouseDown
724                                       location: p
725                                  modifierFlags: 0
726                                      timestamp: [e timestamp]
727                                   windowNumber: [[view window] windowNumber]
728                                        context: [e context]
729                                    eventNumber: 0/*[e eventNumber] */
730                                     clickCount: 1
731                                       pressure: 0];
732   long retVal;
734   context_menu_value = -1;
735   [NSMenu popUpContextMenu: self withEvent: event forView: view];
736   retVal = context_menu_value;
737   context_menu_value = 0;
738   return retVal > 0
739       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
740       : Qnil;
743 @end  /* EmacsMenu */
747 /* ==========================================================================
749     Context Menu: implementing functions
751    ========================================================================== */
753 static Lisp_Object
754 cleanup_popup_menu (Lisp_Object arg)
756   discard_menu_items ();
757   return Qnil;
761 static Lisp_Object
762 ns_popup_menu (Lisp_Object position, Lisp_Object menu)
764   EmacsMenu *pmenu;
765   struct frame *f = NULL;
766   NSPoint p;
767   Lisp_Object window, x, y, tem, keymap, title;
768   struct gcpro gcpro1;
769   int specpdl_count = SPECPDL_INDEX (), specpdl_count2;
770   char *error_name = NULL;
771   int keymaps = 0;
772   widget_value *wv, *first_wv = 0;
774   NSTRACE (ns_popup_menu);
776   if (!NILP (position))
777     {
778       check_ns ();
779   
780       if (EQ (position, Qt)
781           || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
782                                    || EQ (XCAR (position), Qtool_bar))))
783         {
784           /* Use the mouse's current position.  */
785           struct frame *new_f = SELECTED_FRAME ();
787           if (FRAME_TERMINAL (new_f)->mouse_position_hook)
788             (*FRAME_TERMINAL (new_f)->mouse_position_hook)
789               (&new_f, 0, 0, 0, &x, &y, 0);
790           if (new_f != 0)
791             XSETFRAME (window, new_f);
792           else
793             {
794               window = selected_window;
795               x = make_number (0);
796               y = make_number (0);
797             }
798         }
799       else
800         {
801           CHECK_CONS (position);
802           tem = Fcar (position);
803           if (XTYPE (tem) == Lisp_Cons)
804             {
805               window = Fcar (Fcdr (position));
806               x = Fcar (tem);
807               y = Fcar (Fcdr (tem));
808             }
809           else
810             {
811               tem = Fcar (Fcdr (position));
812               window = Fcar (tem);
813               tem = Fcar (Fcdr (Fcdr (tem)));
814               x = Fcar (tem);
815               y = Fcdr (tem);
816             }
817         }
818   
819       CHECK_NUMBER (x);
820       CHECK_NUMBER (y);
822       if (FRAMEP (window))
823         {
824           f = XFRAME (window);
825       
826           p.x = 0;
827           p.y = 0;
828         }
829       else
830         {
831           struct window *win = XWINDOW (window);
832           CHECK_LIVE_WINDOW (window);
833           f = XFRAME (WINDOW_FRAME (win));
834           p.x = FRAME_COLUMN_WIDTH (f) * WINDOW_LEFT_EDGE_COL (win);
835           p.y = FRAME_LINE_HEIGHT (f) * WINDOW_TOP_EDGE_LINE (win);
836         }
838       p.x += XINT (x); p.y += XINT (y);
840       XSETFRAME (Vmenu_updating_frame, f);
841     }
842   else
843     {      /* no position given */
844       /* FIXME: if called during dump, we need to stop precomputation of
845          key equivalents (see below) because the keydefs in ns-win.el have
846          not been loaded yet. */
847       if (noninteractive)
848         return Qnil;
849       Vmenu_updating_frame = Qnil;
850     }
852   /* now parse the lisp menus */
853   record_unwind_protect (unuse_menu_items, Qnil);
854   title = Qnil;
855   GCPRO1 (title);
857   /* Decode the menu items from what was specified.  */
859   keymap = get_keymap (menu, 0, 0);
860   if (CONSP (keymap))
861     {
862       /* We were given a keymap.  Extract menu info from the keymap.  */
863       Lisp_Object prompt;
865       /* Extract the detailed info to make one pane.  */
866       keymap_panes (&menu, 1, NILP (position));
868       /* Search for a string appearing directly as an element of the keymap.
869          That string is the title of the menu.  */
870       prompt = Fkeymap_prompt (keymap);
871       title = NILP (prompt) ? build_string ("Select") : prompt;
873       /* Make that be the pane title of the first pane.  */
874       if (!NILP (prompt) && menu_items_n_panes >= 0)
875         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt;
877       keymaps = 1;
878     }
879   else if (CONSP (menu) && KEYMAPP (XCAR (menu)))
880     {
881       /* We were given a list of keymaps.  */
882       int nmaps = XFASTINT (Flength (menu));
883       Lisp_Object *maps
884         = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object));
885       int i;
887       title = Qnil;
889       /* The first keymap that has a prompt string
890          supplies the menu title.  */
891       for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem))
892         {
893           Lisp_Object prompt;
895           maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0);
897           prompt = Fkeymap_prompt (keymap);
898           if (NILP (title) && !NILP (prompt))
899             title = prompt;
900         }
902       /* Extract the detailed info to make one pane.  */
903       keymap_panes (maps, nmaps, NILP (position));
905       /* Make the title be the pane title of the first pane.  */
906       if (!NILP (title) && menu_items_n_panes >= 0)
907         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title;
909       keymaps = 1;
910     }
911   else
912     {
913       /* We were given an old-fashioned menu.  */
914       title = Fcar (menu);
915       CHECK_STRING (title);
917       list_of_panes (Fcdr (menu));
919       keymaps = 0;
920     }
922   unbind_to (specpdl_count, Qnil);
924   /* If no position given, that was a signal to just precompute and cache
925      key equivalents, which was a side-effect of what we just did. */
926   if (NILP (position))
927     {
928       discard_menu_items ();
929       UNGCPRO;
930       return Qnil;
931     }
933   record_unwind_protect (cleanup_popup_menu, Qnil);
934   BLOCK_INPUT;
936   /* now parse stage 2 as in ns_update_menubar */
937   wv = xmalloc_widget_value ();
938   wv->name = "contextmenu";
939   wv->value = 0;
940   wv->enabled = 1;
941   wv->button_type = BUTTON_TYPE_NONE;
942   wv->help = Qnil;
943   first_wv = wv;
945   specpdl_count2 = SPECPDL_INDEX ();
947 #if 0
948   /* FIXME: a couple of one-line differences prevent reuse */
949   wv = digest_single_submenu (0, menu_items_used, Qnil);
950 #else
951   {
952   widget_value *save_wv = 0, *prev_wv = 0;
953   widget_value **submenu_stack
954     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
955 /*   Lisp_Object *subprefix_stack
956        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
957   int submenu_depth = 0;
958   int first_pane = 1;
959   int i;
961   /* Loop over all panes and items, filling in the tree.  */
962   i = 0;
963   while (i < menu_items_used)
964     {
965       if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
966         {
967           submenu_stack[submenu_depth++] = save_wv;
968           save_wv = prev_wv;
969           prev_wv = 0;
970           first_pane = 1;
971           i++;
972         }
973       else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
974         {
975           prev_wv = save_wv;
976           save_wv = submenu_stack[--submenu_depth];
977           first_pane = 0;
978           i++;
979         }
980       else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
981                && submenu_depth != 0)
982         i += MENU_ITEMS_PANE_LENGTH;
983       /* Ignore a nil in the item list.
984          It's meaningful only for dialog boxes.  */
985       else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
986         i += 1;
987       else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
988         {
989           /* Create a new pane.  */
990           Lisp_Object pane_name, prefix;
991           char *pane_string;
993           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
994           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
996 #ifndef HAVE_MULTILINGUAL_MENU
997           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
998             {
999               pane_name = ENCODE_MENU_STRING (pane_name);
1000               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
1001             }
1002 #endif
1003           pane_string = (NILP (pane_name)
1004                          ? "" : (char *) SDATA (pane_name));
1005           /* If there is just one top-level pane, put all its items directly
1006              under the top-level menu.  */
1007           if (menu_items_n_panes == 1)
1008             pane_string = "";
1010           /* If the pane has a meaningful name,
1011              make the pane a top-level menu item
1012              with its items as a submenu beneath it.  */
1013           if (!keymaps && strcmp (pane_string, ""))
1014             {
1015               wv = xmalloc_widget_value ();
1016               if (save_wv)
1017                 save_wv->next = wv;
1018               else
1019                 first_wv->contents = wv;
1020               wv->name = pane_string;
1021               if (keymaps && !NILP (prefix))
1022                 wv->name++;
1023               wv->value = 0;
1024               wv->enabled = 1;
1025               wv->button_type = BUTTON_TYPE_NONE;
1026               wv->help = Qnil;
1027               save_wv = wv;
1028               prev_wv = 0;
1029             }
1030           else if (first_pane)
1031             {
1032               save_wv = wv;
1033               prev_wv = 0;
1034             }
1035           first_pane = 0;
1036           i += MENU_ITEMS_PANE_LENGTH;
1037         }
1038       else
1039         {
1040           /* Create a new item within current pane.  */
1041           Lisp_Object item_name, enable, descrip, def, type, selected, help;
1042           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
1043           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
1044           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
1045           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
1046           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
1047           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
1048           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
1050 #ifndef HAVE_MULTILINGUAL_MENU
1051           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
1052             {
1053               item_name = ENCODE_MENU_STRING (item_name);
1054               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
1055             }
1057           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
1058             {
1059               descrip = ENCODE_MENU_STRING (descrip);
1060               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
1061             }
1062 #endif /* not HAVE_MULTILINGUAL_MENU */
1064           wv = xmalloc_widget_value ();
1065           if (prev_wv)
1066             prev_wv->next = wv;
1067           else
1068             save_wv->contents = wv;
1069           wv->name = (char *) SDATA (item_name);
1070           if (!NILP (descrip))
1071             wv->key = (char *) SDATA (descrip);
1072           wv->value = 0;
1073           /* If this item has a null value,
1074              make the call_data null so that it won't display a box
1075              when the mouse is on it.  */
1076           wv->call_data
1077               = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
1078           wv->enabled = !NILP (enable);
1080           if (NILP (type))
1081             wv->button_type = BUTTON_TYPE_NONE;
1082           else if (EQ (type, QCtoggle))
1083             wv->button_type = BUTTON_TYPE_TOGGLE;
1084           else if (EQ (type, QCradio))
1085             wv->button_type = BUTTON_TYPE_RADIO;
1086           else
1087             abort ();
1089           wv->selected = !NILP (selected);
1091           if (! STRINGP (help))
1092             help = Qnil;
1094           wv->help = help;
1096           prev_wv = wv;
1098           i += MENU_ITEMS_ITEM_LENGTH;
1099         }
1100     }
1101   }
1102 #endif
1104   if (!NILP (title))
1105     {
1106       widget_value *wv_title = xmalloc_widget_value ();
1107       widget_value *wv_sep = xmalloc_widget_value ();
1109       /* Maybe replace this separator with a bitmap or owner-draw item
1110          so that it looks better.  Having two separators looks odd.  */
1111       wv_sep->name = "--";
1112       wv_sep->next = first_wv->contents;
1113       wv_sep->help = Qnil;
1115 #ifndef HAVE_MULTILINGUAL_MENU
1116       if (STRING_MULTIBYTE (title))
1117         title = ENCODE_MENU_STRING (title);
1118 #endif
1120       wv_title->name = (char *) SDATA (title);
1121       wv_title->enabled = NO;
1122       wv_title->button_type = BUTTON_TYPE_NONE;
1123       wv_title->help = Qnil;
1124       wv_title->next = wv_sep;
1125       first_wv->contents = wv_title;
1126     }
1128   pmenu = [[EmacsMenu alloc] initWithTitle:
1129                                [NSString stringWithUTF8String: SDATA (title)]];
1130   [pmenu fillWithWidgetValue: first_wv->contents];
1131   free_menubar_widget_value_tree (first_wv);
1132   unbind_to (specpdl_count2, Qnil);
1134   popup_activated_flag = 1;
1135   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1136   popup_activated_flag = 0;
1137   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1139   UNBLOCK_INPUT;
1140   discard_menu_items ();
1141   unbind_to (specpdl_count, Qnil);
1142   UNGCPRO;
1144   if (error_name) error (error_name);
1145   return tem;
1151 /* ==========================================================================
1153     Toolbar: externally-called functions
1155    ========================================================================== */
1157 void
1158 free_frame_tool_bar (FRAME_PTR f)
1159 /* --------------------------------------------------------------------------
1160     Under NS we just hide the toolbar until it might be needed again.
1161    -------------------------------------------------------------------------- */
1163   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1166 void
1167 update_frame_tool_bar (FRAME_PTR f)
1168 /* --------------------------------------------------------------------------
1169     Update toolbar contents
1170    -------------------------------------------------------------------------- */
1172   int i;
1173   EmacsToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar];
1175   [toolbar clearActive];
1177   /* update EmacsToolbar as in GtkUtils, build items list */
1178   for (i = 0; i < f->n_tool_bar_items; ++i)
1179     {
1180 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1181                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1183       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1184       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1185       int idx;
1186       int img_id;
1187       struct image *img;
1188       Lisp_Object image;
1189       Lisp_Object helpObj;
1190       char *helpText;
1192       /* If image is a vector, choose the image according to the
1193          button state.  */
1194       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1195       if (VECTORP (image))
1196         {
1197           /* NS toolbar auto-computes disabled and selected images */
1198           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1199           xassert (ASIZE (image) >= idx);
1200           image = AREF (image, idx);
1201         }
1202       else
1203         {
1204           idx = -1;
1205         }
1206       /* Ignore invalid image specifications.  */
1207       if (!valid_image_p (image))
1208         {
1209           NSLog (@"Invalid image for toolbar item");
1210           continue;
1211         }
1213       img_id = lookup_image (f, image);
1214       img = IMAGE_FROM_ID (f, img_id);
1215       prepare_image_for_display (f, img);
1217       if (img->load_failed_p || img->pixmap == nil)
1218         {
1219           NSLog (@"Could not prepare toolbar image for display.");
1220           continue;
1221         }
1223       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1224       if (NILP (helpObj))
1225         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1226       helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1228       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1229                                enabled: enabled_p];
1230 #undef TOOLPROP
1231     }
1233   if (![toolbar isVisible])
1234       [toolbar setVisible: YES];
1236   if ([toolbar changed])
1237     {
1238       /* inform app that toolbar has changed */
1239       NSDictionary *dict = [toolbar configurationDictionary];
1240       NSMutableDictionary *newDict = [dict mutableCopy];
1241       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1242       NSObject *key;
1243       while ((key = [keys nextObject]) != nil)
1244         {
1245           NSObject *val = [dict objectForKey: key];
1246           if ([val isKindOfClass: [NSArray class]])
1247             {
1248               [newDict setObject:
1249                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1250                           forKey: key];
1251               break;
1252             }
1253         }
1254       [toolbar setConfigurationFromDictionary: newDict];
1255       [newDict release];
1256     }
1261 /* ==========================================================================
1263     Toolbar: class implementation
1265    ========================================================================== */
1267 @implementation EmacsToolbar
1269 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1271   self = [super initWithIdentifier: identifier];
1272   emacsView = view;
1273   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1274   [self setSizeMode: NSToolbarSizeModeSmall];
1275   [self setDelegate: self];
1276   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1277   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1278   prevEnablement = enablement = 0L;
1279   return self;
1282 - (void)dealloc
1284   [prevIdentifiers release];
1285   [activeIdentifiers release];
1286   [identifierToItem release];
1287   [super dealloc];
1290 - (void) clearActive
1292   [prevIdentifiers release];
1293   prevIdentifiers = [activeIdentifiers copy];
1294   [activeIdentifiers removeAllObjects];
1295   prevEnablement = enablement;
1296   enablement = 0L;
1299 - (BOOL) changed
1301   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1302     enablement == prevEnablement ? NO : YES;
1305 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1306                         helpText: (char *)help enabled: (BOOL)enabled
1308   /* 1) come up w/identifier */
1309   NSString *identifier
1310       = [NSString stringWithFormat: @"%u", [img hash]];
1312   /* 2) create / reuse item */
1313   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1314   if (item == nil)
1315     {
1316       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1317                autorelease];
1318       [item setImage: img];
1319       [item setToolTip: [NSString stringWithCString: help]];
1320       [item setTarget: emacsView];
1321       [item setAction: @selector (toolbarClicked:)];
1322     }
1324   [item setTag: idx];
1325   [item setEnabled: enabled];
1327   /* 3) update state */
1328   [identifierToItem setObject: item forKey: identifier];
1329   [activeIdentifiers addObject: identifier];
1330   enablement = (enablement << 1) | (enabled == YES);
1333 /* This overrides super's implementation, which automatically sets
1334    all items to enabled state (for some reason). */
1335 - (void)validateVisibleItems { }
1338 /* delegate methods */
1340 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1341       itemForItemIdentifier: (NSString *)itemIdentifier
1342   willBeInsertedIntoToolbar: (BOOL)flag
1344   /* look up NSToolbarItem by identifier and return... */
1345   return [identifierToItem objectForKey: itemIdentifier];
1348 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1350   /* return entire set.. */
1351   return activeIdentifiers;
1354 /* for configuration palette (not yet supported) */
1355 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1357   /* return entire set... */
1358   return [identifierToItem allKeys];
1361 /* optional and unneeded */
1362 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1363 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1364 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1366 @end  /* EmacsToolbar */
1370 /* ==========================================================================
1372     Tooltip: class implementation
1374    ========================================================================== */
1376 /* Needed because NeXTstep does not provide enough control over tooltip
1377    display. */
1378 @implementation EmacsTooltip
1380 - init
1382   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1383                                             blue: 0.792 alpha: 0.95];
1384   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1385   NSFont *sfont = [font screenFont];
1386   int height = [sfont ascender] - [sfont descender];
1387 /*[font boundingRectForFont].size.height; */
1388   NSRect r = NSMakeRect (0, 0, 100, height+6);
1390   textField = [[NSTextField alloc] initWithFrame: r];
1391   [textField setFont: font];
1392   [textField setBackgroundColor: col];
1394   [textField setEditable: NO];
1395   [textField setSelectable: NO];
1396   [textField setBordered: YES];
1397   [textField setBezeled: YES];
1398   [textField setDrawsBackground: YES];
1400   win = [[NSWindow alloc]
1401             initWithContentRect: [textField frame]
1402                       styleMask: 0
1403                         backing: NSBackingStoreBuffered
1404                           defer: YES];
1405   [win setReleasedWhenClosed: NO];
1406   [win setDelegate: self];
1407   [[win contentView] addSubview: textField];
1408 /*  [win setBackgroundColor: col]; */
1409   [win setOpaque: NO];
1411   return self;
1414 - (void) dealloc
1416   [win close];
1417   [win release];
1418   [textField release];
1419   [super dealloc];
1422 - (void) setText: (char *)text
1424   NSString *str = [NSString stringWithUTF8String: text];
1425   NSRect r = [textField frame];
1426   r.size.width = [[[textField font] screenFont] widthOfString: str] + 8;
1427   [textField setFrame: r];
1428   [textField setStringValue: str];
1431 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1433   NSRect wr = [win frame];
1435   wr.origin = NSMakePoint (x, y);
1436   wr.size = [textField frame].size;
1438   [win setFrame: wr display: YES];
1439   [win orderFront: self];
1440   [win display];
1441   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1442                                          selector: @selector (hide)
1443                                          userInfo: nil repeats: NO];
1444   [timer retain];
1447 - (void) hide
1449   [win close];
1450   if (timer != nil)
1451     {
1452       if ([timer isValid])
1453         [timer invalidate];
1454       [timer release];
1455       timer = nil;
1456     }
1459 - (BOOL) isActive
1461   return timer != nil;
1464 - (NSRect) frame
1466   return [textField frame];
1469 @end  /* EmacsTooltip */
1473 /* ==========================================================================
1475     Popup Dialog: implementing functions
1477    ========================================================================== */
1479 Lisp_Object
1480 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1482   id dialog;
1483   Lisp_Object window, tem;
1484   struct frame *f;
1485   NSPoint p;
1486   BOOL isQ;
1488   NSTRACE (x-popup-dialog);
1489   
1490   check_ns ();
1492   isQ = NILP (header);
1494   if (EQ (position, Qt))
1495     {
1496       window = selected_window;
1497     }
1498   else if (CONSP (position))
1499     {
1500       Lisp_Object tem;
1501       tem = Fcar (position);
1502       if (XTYPE (tem) == Lisp_Cons)
1503         window = Fcar (Fcdr (position));
1504       else
1505         {
1506           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1507           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1508         }
1509     }
1510   else if (FRAMEP (position))
1511     {
1512       window = position;
1513     }
1514   else
1515     {
1516       CHECK_LIVE_WINDOW (position);
1517       window = position;
1518     }
1519   
1520   if (FRAMEP (window))
1521     f = XFRAME (window);
1522   else
1523     {
1524       CHECK_LIVE_WINDOW (window);
1525       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1526     }
1527   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1528   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1529   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1530                                            isQuestion: isQ];
1531   popup_activated_flag = 1;
1532   tem = [dialog runDialogAt: p];
1533   popup_activated_flag = 0;
1535   [dialog close];
1537   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1538   return tem;
1542 /* ==========================================================================
1544     Popup Dialog: class implementation
1546    ========================================================================== */
1548 @interface FlippedView : NSView
1551 @end
1553 @implementation FlippedView
1554 - (BOOL)isFlipped
1556   return YES;
1558 @end
1560 @implementation EmacsDialogPanel
1562 #define SPACER          8.0
1563 #define ICONSIZE        64.0
1564 #define TEXTHEIGHT      20.0
1565 #define MINCELLWIDTH    90.0
1567 - initWithContentRect: (NSRect)contentRect styleMask: (unsigned int)aStyle
1568               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1570   NSSize spacing = {SPACER, SPACER};
1571   NSRect area;
1572   char this_cmd_name[80];
1573   id cell;
1574   static NSImageView *imgView;
1575   static FlippedView *contentView;
1577   if (imgView == nil)
1578     {
1579       NSImage *img;
1580       area.origin.x   = 3*SPACER;
1581       area.origin.y   = 2*SPACER;
1582       area.size.width = ICONSIZE;
1583       area.size.height= ICONSIZE;
1584       img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1585       [img setScalesWhenResized: YES];
1586       [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1587       imgView = [[NSImageView alloc] initWithFrame: area];
1588       [imgView setImage: img];
1589       [imgView setEditable: NO];
1590       [img release];
1591     }
1593   aStyle = NSTitledWindowMask;
1594   flag = YES;
1595   rows = 0;
1596   cols = 1;
1597   [super initWithContentRect: contentRect styleMask: aStyle
1598                      backing: backingType defer: flag];
1599   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1600   [self setContentView: contentView];
1602   [[self contentView] setAutoresizesSubviews: YES];
1604   [[self contentView] addSubview: imgView];
1605   [self setTitle: @""];
1607   area.origin.x   += ICONSIZE+2*SPACER;
1608 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1609   area.size.width = 400;
1610   area.size.height= TEXTHEIGHT;
1611   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1612   [[self contentView] addSubview: command];
1613   [command setStringValue: @"Emacs"];
1614   [command setDrawsBackground: NO];
1615   [command setBezeled: NO];
1616   [command setSelectable: NO];
1617   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1619 /*  area.origin.x   = ICONSIZE+2*SPACER;
1620   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1621   area.size.width = 400;
1622   area.size.height= 2;
1623   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1624   [[self contentView] addSubview: tem];
1625   [tem setTitlePosition: NSNoTitle];
1626   [tem setAutoresizingMask: NSViewWidthSizable];*/
1628 /*  area.origin.x = ICONSIZE+2*SPACER; */
1629   area.origin.y += TEXTHEIGHT+SPACER;
1630   area.size.width = 400;
1631   area.size.height= TEXTHEIGHT;
1632   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1633   [[self contentView] addSubview: title];
1634   [title setDrawsBackground: NO];
1635   [title setBezeled: NO];
1636   [title setSelectable: NO];
1637   [title setFont: [NSFont systemFontOfSize: 11.0]];
1639   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1640   [cell setBordered: NO];
1641   [cell setEnabled: NO];
1642   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1643   [cell setBezelStyle: NSRoundedBezelStyle];
1645   matrix = [[NSMatrix alloc] initWithFrame: contentRect 
1646                                       mode: NSHighlightModeMatrix 
1647                                  prototype: cell 
1648                               numberOfRows: 0 
1649                            numberOfColumns: 1];
1650   [[self contentView] addSubview: matrix];
1651   [matrix release];
1652   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1653                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1654   [matrix setIntercellSpacing: spacing];
1656   [self setOneShot: YES];
1657   [self setReleasedWhenClosed: YES];
1658   [self setHidesOnDeactivate: YES];
1659   return self;
1663 - (BOOL)windowShouldClose: (id)sender
1665   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1666   return NO;
1670 void process_dialog (id window, Lisp_Object list)
1672   Lisp_Object item;
1673   int row = 0;
1675   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1676     {
1677       item = XCAR (list);
1678       if (XTYPE (item) == Lisp_String)
1679         {
1680           [window addString: XSTRING (item)->data row: row++];
1681         }
1682       else if (XTYPE (item) == Lisp_Cons)
1683         {
1684           [window addButton: XSTRING (XCAR (item))->data
1685                       value: XCDR (item) row: row++];
1686         }
1687       else if (NILP (item))
1688         {
1689           [window addSplit];
1690           row = 0;
1691         }
1692     }
1696 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1698   id cell;
1699        
1700   if (row >= rows)
1701     {
1702       [matrix addRow];
1703       rows++;
1704     }
1705   cell = [matrix cellAtRow: row column: cols-1];
1706   [cell setTarget: self];
1707   [cell setAction: @selector (clicked: )];
1708   [cell setTitle: [NSString stringWithUTF8String: str]];
1709   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1710   [cell setBordered: YES];
1711   [cell setEnabled: YES];
1713   return self;
1717 - addString: (char *)str row: (int)row
1719   id cell;
1720        
1721   if (row >= rows)
1722     {
1723       [matrix addRow];
1724       rows++;
1725     }
1726   cell = [matrix cellAtRow: row column: cols-1];
1727   [cell setTitle: [NSString stringWithUTF8String: str]];
1728   [cell setBordered: YES];
1729   [cell setEnabled: NO];
1731   return self;
1735 - addSplit
1737   [matrix addColumn];
1738   cols++;
1739   return self;
1743 - clicked: sender
1745   NSArray *sellist = nil;
1746   EMACS_INT seltag;
1748   sellist = [sender selectedCells];
1749   if ([sellist count]<1) 
1750     return self;
1752   seltag = [[sellist objectAtIndex: 0] tag];
1753   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1754     [NSApp stopModalWithCode: seltag];
1755   return self;
1759 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1761   Lisp_Object head;
1762   [super init];
1764   if (XTYPE (contents) == Lisp_Cons)
1765     {
1766       head = Fcar (contents);
1767       process_dialog (self, Fcdr (contents));
1768     }
1769   else
1770     head = contents;
1772   if (XTYPE (head) == Lisp_String)
1773       [title setStringValue:
1774                  [NSString stringWithUTF8String: XSTRING (head)->data]];
1775   else if (isQ == YES)
1776       [title setStringValue: @"Question"];
1777   else
1778       [title setStringValue: @"Information"];
1780   {
1781     int i;
1782     NSRect r, s, t;
1784     if (cols == 1 && rows > 1)  /* Never told where to split */
1785       {
1786         [matrix addColumn];
1787         for (i = 0; i<rows/2; i++)
1788           {
1789             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1790                       atRow: i column: 1];
1791             [matrix removeRow: (rows+1)/2];
1792           }
1793       }
1795     [matrix sizeToFit];
1796     {
1797       NSSize csize = [matrix cellSize];
1798       if (csize.width < MINCELLWIDTH)
1799         {
1800           csize.width = MINCELLWIDTH;
1801           [matrix setCellSize: csize];
1802           [matrix sizeToCells];
1803         }
1804     }
1806     [title sizeToFit];
1807     [command sizeToFit];
1809     t = [matrix frame];
1810     r = [title frame];
1811     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1812       {
1813         t.origin.x   = r.origin.x;
1814         t.size.width = r.size.width;
1815       }
1816     r = [command frame];
1817     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1818       {
1819         t.origin.x   = r.origin.x;
1820         t.size.width = r.size.width;
1821       }
1823     r = [self frame];
1824     s = [(NSView *)[self contentView] frame];
1825     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1826     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1827     [self setFrame: r display: NO];
1828   }
1830   return self;
1834 - (void)dealloc
1836   { [super dealloc]; return; };
1840 - (Lisp_Object)runDialogAt: (NSPoint)p
1842   NSEvent *e;
1843   NSModalSession session;
1844   int ret;
1846   [self center];  /*XXX p ignored? */
1847   [self orderFront: NSApp];
1849   session = [NSApp beginModalSessionForWindow: self];
1850   while ((ret = [NSApp runModalSession: session]) == NSRunContinuesResponse)
1851     {
1852     (e = [NSApp nextEventMatchingMask: NSAnyEventMask
1853                             untilDate: [NSDate distantFuture]
1854                                inMode: NSEventTrackingRunLoopMode
1855                               dequeue: NO]);
1856 /*fprintf (stderr, "ret = %d\te = %p\n", ret, e); */
1857     }
1858   [NSApp endModalSession: session];
1860   {                             // FIXME: BIG UGLY HACK!!!
1861       Lisp_Object tmp;
1862       *(EMACS_INT*)(&tmp) = ret;
1863       return tmp;
1864   }
1867 @end
1871 /* ==========================================================================
1873     Lisp definitions
1875    ========================================================================== */
1877 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1878        doc: /* Cause the NS menu to be re-calculated.  */)
1879      ()
1881   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1882   return Qnil;
1886 DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
1887        doc: /* Pop up a deck-of-cards menu and return user's selection.
1888 POSITION is a position specification.  This is either a mouse button event
1889 or a list ((XOFFSET YOFFSET) WINDOW)
1890 where XOFFSET and YOFFSET are positions in pixels from the top left
1891 corner of WINDOW.  (WINDOW may be a window or a frame object.)
1892 This controls the position of the top left of the menu as a whole.
1893 If POSITION is t, it means to use the current mouse position.
1895 MENU is a specifier for a menu.  For the simplest case, MENU is a keymap.
1896 The menu items come from key bindings that have a menu string as well as
1897 a definition; actually, the \"definition\" in such a key binding looks like
1898 \(STRING . REAL-DEFINITION).  To give the menu a title, put a string into
1899 the keymap as a top-level element.
1901 If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.
1902 Otherwise, REAL-DEFINITION should be a valid key binding definition.
1904 You can also use a list of keymaps as MENU.
1905   Then each keymap makes a separate pane.
1907 When MENU is a keymap or a list of keymaps, the return value is the
1908 list of events corresponding to the user's choice. Note that
1909 `x-popup-menu' does not actually execute the command bound to that
1910 sequence of events.
1912 Alternatively, you can specify a menu of multiple panes
1913   with a list of the form (TITLE PANE1 PANE2...),
1914 where each pane is a list of form (TITLE ITEM1 ITEM2...).
1915 Each ITEM is normally a cons cell (STRING . VALUE);
1916 but a string can appear as an item--that makes a nonselectable line
1917 in the menu.
1918 With this form of menu, the return value is VALUE from the chosen item.  */)
1919      (position, menu)
1920      Lisp_Object position, menu;
1922   return ns_popup_menu (position, menu);
1926 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1927        doc: /* Pop up a dialog box and return user's selection.
1928 POSITION specifies which frame to use.
1929 This is normally a mouse button event or a window or frame.
1930 If POSITION is t, it means to use the frame the mouse is on.
1931 The dialog box appears in the middle of the specified frame.
1933 CONTENTS specifies the alternatives to display in the dialog box.
1934 It is a list of the form (DIALOG ITEM1 ITEM2...).
1935 Each ITEM is a cons cell (STRING . VALUE).
1936 The return value is VALUE from the chosen item.
1938 An ITEM may also be just a string--that makes a nonselectable item.
1939 An ITEM may also be nil--that means to put all preceding items
1940 on the left of the dialog box and all following items on the right.
1941 \(By default, approximately half appear on each side.)
1943 If HEADER is non-nil, the frame title for the box is "Information",
1944 otherwise it is "Question".
1946 If the user gets rid of the dialog box without making a valid choice,
1947 for instance using the window manager, then this produces a quit and
1948 `x-popup-dialog' does not return.  */)
1949      (position, contents, header)
1950      Lisp_Object position, contents, header;
1952   return ns_popup_dialog (position, contents, header);
1955 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1956        doc: /* Return t if a menu or popup dialog is active.  */)
1957      ()
1959   return popup_activated () ? Qt : Qnil;
1962 /* ==========================================================================
1964     Lisp interface declaration
1966    ========================================================================== */
1968 void
1969 syms_of_nsmenu ()
1971   defsubr (&Sx_popup_menu);
1972   defsubr (&Sx_popup_dialog);
1973   defsubr (&Sns_reset_menu);
1974   defsubr (&Smenu_or_popup_active_p);
1975   staticpro (&menu_items);
1976   menu_items = Qnil;
1978   Qdebug_on_next_call = intern ("debug-on-next-call");
1979   staticpro (&Qdebug_on_next_call);
1982 // arch-tag: 75773656-52e5-4c44-a398-47bd87b32619