* process.c (allocate_pty) [PTY_OPEN]: Set fd's FD_CLOEXEC flag.
[emacs.git] / src / nsmenu.m
blob5af813ac7586a96cc05b3816829214cda9c2e7ff
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007-2013 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 "character.h"
31 #include "buffer.h"
32 #include "keymap.h"
33 #include "coding.h"
34 #include "commands.h"
35 #include "blockinput.h"
36 #include "nsterm.h"
37 #include "termhooks.h"
38 #include "keyboard.h"
39 #include "menu.h"
41 #define NSMENUPROFILE 0
43 #if NSMENUPROFILE
44 #include <sys/timeb.h>
45 #include <sys/types.h>
46 #endif
48 #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 Lisp_Object Qdebug_on_next_call;
66 extern Lisp_Object 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 /* Nonzero means we are tracking and updating menus.  */
75 static int trackingMenu;
78 /* NOTE: toolbar implementation is at end,
79   following complete menu implementation. */
82 /* ==========================================================================
84     Menu: Externally-called functions
86    ========================================================================== */
89 /* Supposed to discard menubar and free storage.  Since we share the
90    menubar among frames and update its context for the focused window,
91    there is nothing to do here. */
92 void
93 free_frame_menubar (struct frame *f)
95   return;
99 int
100 popup_activated (void)
102   return popup_activated_flag;
106 /* --------------------------------------------------------------------------
107     Update menubar.  Three cases:
108     1) ! deep_p, submenu = nil: Fresh switch onto a frame -- either set up
109        just top-level menu strings (OS X), or goto case (2) (GNUstep).
110     2) deep_p, submenu = nil: Recompute all submenus.
111     3) deep_p, submenu = non-nil: Update contents of a single submenu.
112    -------------------------------------------------------------------------- */
113 static void
114 ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu)
116   NSAutoreleasePool *pool;
117   id menu = [NSApp mainMenu];
118   static EmacsMenu *last_submenu = nil;
119   BOOL needsSet = NO;
120   bool owfi;
121   Lisp_Object items;
122   widget_value *wv, *first_wv, *prev_wv = 0;
123   int i;
125 #if NSMENUPROFILE
126   struct timeb tb;
127   long t;
128 #endif
130   NSTRACE (ns_update_menubar);
132   if (f != SELECTED_FRAME ())
133       return;
134   XSETFRAME (Vmenu_updating_frame, f);
135 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
137   block_input ();
138   pool = [[NSAutoreleasePool alloc] init];
140   /* Menu may have been created automatically; if so, discard it. */
141   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
142     {
143       [menu release];
144       menu = nil;
145     }
147   if (menu == nil)
148     {
149       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
150       needsSet = YES;
151     }
152   else
153     {  /* close up anything on there */
154       id attMenu = [menu attachedMenu];
155       if (attMenu != nil)
156         [attMenu close];
157     }
159 #if NSMENUPROFILE
160   ftime (&tb);
161   t = -(1000*tb.time+tb.millitm);
162 #endif
164 #ifdef NS_IMPL_GNUSTEP
165   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
166 #endif
168   if (deep_p)
169     {
170       /* Fully parse one or more of the submenus. */
171       int n = 0;
172       int *submenu_start, *submenu_end;
173       bool *submenu_top_level_items;
174       int *submenu_n_panes;
175       struct buffer *prev = current_buffer;
176       Lisp_Object buffer;
177       ptrdiff_t specpdl_count = SPECPDL_INDEX ();
178       int previous_menu_items_used = f->menu_bar_items_used;
179       Lisp_Object *previous_items
180         = alloca (previous_menu_items_used * sizeof *previous_items);
182       /* lisp preliminaries */
183       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->contents;
184       specbind (Qinhibit_quit, Qt);
185       specbind (Qdebug_on_next_call, Qnil);
186       record_unwind_save_match_data ();
187       if (NILP (Voverriding_local_map_menu_flag))
188         {
189           specbind (Qoverriding_terminal_local_map, Qnil);
190           specbind (Qoverriding_local_map, Qnil);
191         }
192       set_buffer_internal_1 (XBUFFER (buffer));
194       /* TODO: for some reason this is not needed in other terms,
195            but some menu updates call Info-extract-pointer which causes
196            abort-on-error if waiting-for-input.  Needs further investigation. */
197       owfi = waiting_for_input;
198       waiting_for_input = 0;
200       /* lucid hook and possible reset */
201       safe_run_hooks (Qactivate_menubar_hook);
202       if (! NILP (Vlucid_menu_bar_dirty_flag))
203         call0 (Qrecompute_lucid_menubar);
204       safe_run_hooks (Qmenu_bar_update_hook);
205       fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
207       /* Now ready to go */
208       items = FRAME_MENU_BAR_ITEMS (f);
210       /* Save the frame's previous menu bar contents data */
211       if (previous_menu_items_used)
212         memcpy (previous_items, aref_addr (f->menu_bar_vector, 0),
213                 previous_menu_items_used * sizeof (Lisp_Object));
215       /* parse stage 1: extract from lisp */
216       save_menu_items ();
218       menu_items = f->menu_bar_vector;
219       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
220       submenu_start = alloca (ASIZE (items) * sizeof *submenu_start);
221       submenu_end = alloca (ASIZE (items) * sizeof *submenu_end);
222       submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes);
223       submenu_top_level_items = alloca (ASIZE (items)
224                                         * sizeof *submenu_top_level_items);
225       init_menu_items ();
226       for (i = 0; i < ASIZE (items); i += 4)
227         {
228           Lisp_Object key, string, maps;
230           key = AREF (items, i);
231           string = AREF (items, i + 1);
232           maps = AREF (items, i + 2);
233           if (NILP (string))
234             break;
236           /* FIXME: we'd like to only parse the needed submenu, but this
237                was causing crashes in the _common parsing code.. need to make
238                sure proper initialization done.. */
239 /*        if (submenu && strcmp ([[submenu title] UTF8String], SSDATA (string)))
240              continue; */
242           submenu_start[i] = menu_items_used;
244           menu_items_n_panes = 0;
245           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
246           submenu_n_panes[i] = menu_items_n_panes;
247           submenu_end[i] = menu_items_used;
248           n++;
249         }
251       finish_menu_items ();
252       waiting_for_input = owfi;
255       if (submenu && n == 0)
256         {
257           /* should have found a menu for this one but didn't */
258           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
259                   [[submenu title] UTF8String]);
260           discard_menu_items ();
261           unbind_to (specpdl_count, Qnil);
262           [pool release];
263           unblock_input ();
264           return;
265         }
267       /* parse stage 2: insert into lucid 'widget_value' structures
268          [comments in other terms say not to evaluate lisp code here] */
269       wv = xmalloc_widget_value ();
270       wv->name = "menubar";
271       wv->value = 0;
272       wv->enabled = 1;
273       wv->button_type = BUTTON_TYPE_NONE;
274       wv->help = Qnil;
275       first_wv = wv;
277       for (i = 0; i < 4*n; i += 4)
278         {
279           menu_items_n_panes = submenu_n_panes[i];
280           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
281                                       submenu_top_level_items[i]);
282           if (prev_wv)
283             prev_wv->next = wv;
284           else
285             first_wv->contents = wv;
286           /* Don't set wv->name here; GC during the loop might relocate it.  */
287           wv->enabled = 1;
288           wv->button_type = BUTTON_TYPE_NONE;
289           prev_wv = wv;
290         }
292       set_buffer_internal_1 (prev);
294       /* Compare the new menu items with previous, and leave off if no change */
295       /* FIXME: following other terms here, but seems like this should be
296            done before parse stage 2 above, since its results aren't used */
297       if (previous_menu_items_used
298           && (!submenu || (submenu && submenu == last_submenu))
299           && menu_items_used == previous_menu_items_used)
300         {
301           for (i = 0; i < previous_menu_items_used; i++)
302             /* FIXME: this ALWAYS fails on Buffers menu items.. something
303                  about their strings causes them to change every time, so we
304                  double-check failures */
305             if (!EQ (previous_items[i], AREF (menu_items, i)))
306               if (!(STRINGP (previous_items[i])
307                     && STRINGP (AREF (menu_items, i))
308                     && !strcmp (SSDATA (previous_items[i]),
309                                 SSDATA (AREF (menu_items, i)))))
310                   break;
311           if (i == previous_menu_items_used)
312             {
313               /* No change.. */
315 #if NSMENUPROFILE
316               ftime (&tb);
317               t += 1000*tb.time+tb.millitm;
318               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
319 #endif
321               free_menubar_widget_value_tree (first_wv);
322               discard_menu_items ();
323               unbind_to (specpdl_count, Qnil);
324               [pool release];
325               unblock_input ();
326               return;
327             }
328         }
329       /* The menu items are different, so store them in the frame */
330       /* FIXME: this is not correct for single-submenu case */
331       fset_menu_bar_vector (f, menu_items);
332       f->menu_bar_items_used = menu_items_used;
334       /* Calls restore_menu_items, etc., as they were outside */
335       unbind_to (specpdl_count, Qnil);
337       /* Parse stage 2a: now GC cannot happen during the lifetime of the
338          widget_value, so it's safe to store data from a Lisp_String */
339       wv = first_wv->contents;
340       for (i = 0; i < ASIZE (items); i += 4)
341         {
342           Lisp_Object string;
343           string = AREF (items, i + 1);
344           if (NILP (string))
345             break;
347           wv->name = SSDATA (string);
348           update_submenu_strings (wv->contents);
349           wv = wv->next;
350         }
352       /* Now, update the NS menu; if we have a submenu, use that, otherwise
353          create a new menu for each sub and fill it. */
354       if (submenu)
355         {
356           const char *submenuTitle = [[submenu title] UTF8String];
357           for (wv = first_wv->contents; wv; wv = wv->next)
358             {
359               if (!strcmp (submenuTitle, wv->name))
360                 {
361                   [submenu fillWithWidgetValue: wv->contents];
362                   last_submenu = submenu;
363                   break;
364                 }
365             }
366         }
367       else
368         {
369           [menu fillWithWidgetValue: first_wv->contents frame: f];
370         }
372     }
373   else
374     {
375       static int n_previous_strings = 0;
376       static char previous_strings[100][10];
377       static struct frame *last_f = NULL;
378       int n;
379       Lisp_Object string;
381       wv = xmalloc_widget_value ();
382       wv->name = "menubar";
383       wv->value = 0;
384       wv->enabled = 1;
385       wv->button_type = BUTTON_TYPE_NONE;
386       wv->help = Qnil;
387       first_wv = wv;
389       /* Make widget-value tree w/ just the top level menu bar strings */
390       items = FRAME_MENU_BAR_ITEMS (f);
391       if (NILP (items))
392         {
393           free_menubar_widget_value_tree (first_wv);
394           [pool release];
395           unblock_input ();
396           return;
397         }
400       /* check if no change.. this mechanism is a bit rough, but ready */
401       n = ASIZE (items) / 4;
402       if (f == last_f && n_previous_strings == n)
403         {
404           for (i = 0; i<n; i++)
405             {
406               string = AREF (items, 4*i+1);
408               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
409                 continue;
410               if (NILP (string))
411                 {
412                   if (previous_strings[i][0])
413                     break;
414                   else
415                     continue;
416                 }
417               else if (memcmp (previous_strings[i], SDATA (string),
418                           min (10, SBYTES (string) + 1)))
419                 break;
420             }
422           if (i == n)
423             {
424               free_menubar_widget_value_tree (first_wv);
425               [pool release];
426               unblock_input ();
427               return;
428             }
429         }
431       [menu clear];
432       for (i = 0; i < ASIZE (items); i += 4)
433         {
434           string = AREF (items, i + 1);
435           if (NILP (string))
436             break;
438           if (n < 100)
439             memcpy (previous_strings[i/4], SDATA (string),
440                     min (10, SBYTES (string) + 1));
442           wv = xmalloc_widget_value ();
443           wv->name = SSDATA (string);
444           wv->value = 0;
445           wv->enabled = 1;
446           wv->button_type = BUTTON_TYPE_NONE;
447           wv->help = Qnil;
448           wv->call_data = (void *) (intptr_t) (-1);
450 #ifdef NS_IMPL_COCOA
451           /* we'll update the real copy under app menu when time comes */
452           if (!strcmp ("Services", wv->name))
453             {
454               /* but we need to make sure it will update on demand */
455               [svcsMenu setFrame: f];
456             }
457           else
458 #endif
459           [menu addSubmenuWithTitle: wv->name forFrame: f];
461           if (prev_wv)
462             prev_wv->next = wv;
463           else
464             first_wv->contents = wv;
465           prev_wv = wv;
466         }
468       last_f = f;
469       if (n < 100)
470         n_previous_strings = n;
471       else
472         n_previous_strings = 0;
474     }
475   free_menubar_widget_value_tree (first_wv);
478 #if NSMENUPROFILE
479   ftime (&tb);
480   t += 1000*tb.time+tb.millitm;
481   fprintf (stderr, "Menu update took %ld msec.\n", t);
482 #endif
484   /* set main menu */
485   if (needsSet)
486     [NSApp setMainMenu: menu];
488   [pool release];
489   unblock_input ();
494 /* Main emacs core entry point for menubar menus: called to indicate that the
495    frame's menus have changed, and the *step representation should be updated
496    from Lisp. */
497 void
498 set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
500   ns_update_menubar (f, deep_p, nil);
503 void
504 x_activate_menubar (struct frame *f)
506 #ifdef NS_IMPL_COCOA
507 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
508   ns_update_menubar (f, true, nil);
509   ns_check_pending_open_menu ();
510 #endif
511 #endif
517 /* ==========================================================================
519     Menu: class implementation
521    ========================================================================== */
524 /* Menu that can define itself from Emacs "widget_value"s and will lazily
525    update itself when user clicked.  Based on Carbon/AppKit implementation
526    by Yamamoto Mitsuharu. */
527 @implementation EmacsMenu
529 /* override designated initializer */
530 - initWithTitle: (NSString *)title
532   frame = 0;
533   if ((self = [super initWithTitle: title]))
534     [self setAutoenablesItems: NO];
535   return self;
539 /* used for top-level */
540 - initWithTitle: (NSString *)title frame: (struct frame *)f
542   [self initWithTitle: title];
543   frame = f;
544 #ifdef NS_IMPL_COCOA
545   [self setDelegate: self];
546 #endif
547   return self;
551 - (void)setFrame: (struct frame *)f
553   frame = f;
556 #ifdef NS_IMPL_COCOA
557 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
558 extern NSString *NSMenuDidBeginTrackingNotification;
559 #endif
560 #endif
562 #ifdef NS_IMPL_COCOA
563 -(void)trackingNotification:(NSNotification *)notification
565   /* Update menu in menuNeedsUpdate only while tracking menus.  */
566   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
567                   ? 1 : 0);
568 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
569   if (! trackingMenu) ns_check_menu_open (nil);
570 #endif
573 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
574 - (void)menuWillOpen:(NSMenu *)menu
576   ++trackingMenu;
578 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
579   // On 10.6 we get repeated calls, only the one for NSSystemDefined is "real".
580   if ([[NSApp currentEvent] type] != NSSystemDefined) return;
581 #endif
583   /* When dragging from one menu to another, we get willOpen followed by didClose,
584      i.e. trackingMenu == 3 in willOpen and then 2 after didClose.
585      We have updated all menus, so avoid doing it when trackingMenu == 3.  */
586   if (trackingMenu == 2)
587     ns_check_menu_open (menu);
590 - (void)menuDidClose:(NSMenu *)menu
592   --trackingMenu;
594 #endif /* OSX >= 10.5 */
596 #endif /* NS_IMPL_COCOA */
598 /* delegate method called when a submenu is being opened: run a 'deep' call
599    to set_frame_menubar */
600 - (void)menuNeedsUpdate: (NSMenu *)menu
602   if (!FRAME_LIVE_P (frame))
603     return;
605   /* Cocoa/Carbon will request update on every keystroke
606      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
607      since key equivalents are handled through emacs.
608      On Leopard, even keystroke events generate SystemDefined event.
609      Third-party applications that enhance mouse / trackpad
610      interaction, or also VNC/Remote Desktop will send events
611      of type AppDefined rather than SysDefined.
612      Menus will fail to show up if they haven't been initialized.
613      AppDefined events may lack timing data.
615      Thus, we rely on the didBeginTrackingNotification notification
616      as above to indicate the need for updates.
617      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
618      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
619   */
620   if (trackingMenu == 0)
621     return;
622 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
623 #if (! defined (NS_IMPL_COCOA) \
624      || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5)
625   /* Don't know how to do this for anything other than OSX >= 10.5
626      This is wrong, as it might run Lisp code in the event loop.  */
627   ns_update_menubar (frame, true, self);
628 #endif
632 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
634   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
635       && FRAME_NS_VIEW (SELECTED_FRAME ()))
636     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
637   return YES;
641 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
642    into an accelerator string.  We are only able to display a single character
643    for an accelerator, together with an optional modifier combination.  (Under
644    Carbon more control was possible, but in Cocoa multi-char strings passed to
645    NSMenuItem get ignored.  For now we try to display a super-single letter
646    combo, and return the others as strings to be appended to the item title.
647    (This is signaled by setting keyEquivModMask to 0 for now.) */
648 -(NSString *)parseKeyEquiv: (const char *)key
650   const char *tpos = key;
651   keyEquivModMask = NSCommandKeyMask;
653   if (!key || !strlen (key))
654     return @"";
656   while (*tpos == ' ' || *tpos == '(')
657     tpos++;
658   if ((*tpos == 's') && (*(tpos+1) == '-'))
659     {
660       return [NSString stringWithFormat: @"%c", tpos[2]];
661     }
662   keyEquivModMask = 0; /* signal */
663   return [NSString stringWithUTF8String: tpos];
667 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
669   NSMenuItem *item;
670   widget_value *wv = (widget_value *)wvptr;
672   if (menu_separator_name_p (wv->name))
673     {
674       item = [NSMenuItem separatorItem];
675       [self addItem: item];
676     }
677   else
678     {
679       NSString *title, *keyEq;
680       title = [NSString stringWithUTF8String: wv->name];
681       if (title == nil)
682         title = @"< ? >";  /* (get out in the open so we know about it) */
684       keyEq = [self parseKeyEquiv: wv->key];
685 #ifdef NS_IMPL_COCOA
686       /* OS X just ignores modifier strings longer than one character */
687       if (keyEquivModMask == 0)
688         title = [title stringByAppendingFormat: @" (%@)", keyEq];
689 #endif
691       item = [self addItemWithTitle: (NSString *)title
692                              action: @selector (menuDown:)
693                       keyEquivalent: keyEq];
694       [item setKeyEquivalentModifierMask: keyEquivModMask];
696       [item setEnabled: wv->enabled];
698       /* Draw radio buttons and tickboxes */
699       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
700                            wv->button_type == BUTTON_TYPE_RADIO))
701         [item setState: NSOnState];
702       else
703         [item setState: NSOffState];
705       [item setTag: (NSInteger)wv->call_data];
706     }
708   return item;
712 /* convenience */
713 -(void)clear
715   int n;
717   for (n = [self numberOfItems]-1; n >= 0; n--)
718     {
719       NSMenuItem *item = [self itemAtIndex: n];
720       NSString *title = [item title];
721       if (([title length] == 0  /* OSX 10.5 */
722            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
723            || [@"Apple" isEqualToString: title]) /* older */
724           && ![item isSeparatorItem])
725         continue;
726       [self removeItemAtIndex: n];
727     }
731 - (void)fillWithWidgetValue: (void *)wvptr
733   [self fillWithWidgetValue: wvptr frame:nil];
736 - (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f
738   widget_value *wv = (widget_value *)wvptr;
740   /* clear existing contents */
741   [self setMenuChangedMessagesEnabled: NO];
742   [self clear];
744   /* add new contents */
745   for (; wv != NULL; wv = wv->next)
746     {
747       NSMenuItem *item = [self addItemWithWidgetValue: wv];
749       if (wv->contents)
750         {
751           EmacsMenu *submenu;
753           if (f)
754             submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f];
755           else
756             submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
758           [self setSubmenu: submenu forItem: item];
759           [submenu fillWithWidgetValue: wv->contents];
760           [submenu release];
761           [item setAction: (SEL)nil];
762         }
763     }
765   [self setMenuChangedMessagesEnabled: YES];
766 #ifdef NS_IMPL_GNUSTEP
767   if ([[self window] isVisible])
768     [self sizeToFit];
769 #endif
773 /* adds an empty submenu and returns it */
774 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
776   NSString *titleStr = [NSString stringWithUTF8String: title];
777   NSMenuItem *item = [self addItemWithTitle: titleStr
778                                      action: (SEL)nil /*@selector (menuDown:) */
779                               keyEquivalent: @""];
780   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
781   [self setSubmenu: submenu forItem: item];
782   [submenu release];
783   return submenu;
786 /* run a menu in popup mode */
787 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
788                  keymaps: (bool)keymaps
790   EmacsView *view = FRAME_NS_VIEW (f);
791   NSEvent *e, *event;
792   long retVal;
794 /*   p = [view convertPoint:p fromView: nil]; */
795   p.y = NSHeight ([view frame]) - p.y;
796   e = [[view window] currentEvent];
797    event = [NSEvent mouseEventWithType: NSRightMouseDown
798                               location: p
799                          modifierFlags: 0
800                              timestamp: [e timestamp]
801                           windowNumber: [[view window] windowNumber]
802                                context: [e context]
803                            eventNumber: 0/*[e eventNumber] */
804                             clickCount: 1
805                               pressure: 0];
807   context_menu_value = -1;
808   [NSMenu popUpContextMenu: self withEvent: event forView: view];
809   retVal = context_menu_value;
810   context_menu_value = 0;
811   return retVal > 0
812       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
813       : Qnil;
816 @end  /* EmacsMenu */
820 /* ==========================================================================
822     Context Menu: implementing functions
824    ========================================================================== */
826 Lisp_Object
827 ns_menu_show (struct frame *f, int x, int y, bool for_click, bool keymaps,
828               Lisp_Object title, const char **error)
830   EmacsMenu *pmenu;
831   NSPoint p;
832   Lisp_Object tem;
833   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
834   widget_value *wv, *first_wv = 0;
836   p.x = x; p.y = y;
838   /* now parse stage 2 as in ns_update_menubar */
839   wv = xmalloc_widget_value ();
840   wv->name = "contextmenu";
841   wv->value = 0;
842   wv->enabled = 1;
843   wv->button_type = BUTTON_TYPE_NONE;
844   wv->help = Qnil;
845   first_wv = wv;
847 #if 0
848   /* FIXME: a couple of one-line differences prevent reuse */
849   wv = digest_single_submenu (0, menu_items_used, 0);
850 #else
851   {
852   widget_value *save_wv = 0, *prev_wv = 0;
853   widget_value **submenu_stack
854     = alloca (menu_items_used * sizeof *submenu_stack);
855 /*   Lisp_Object *subprefix_stack
856        = alloca (menu_items_used * sizeof *subprefix_stack); */
857   int submenu_depth = 0;
858   int first_pane = 1;
859   int i;
861   /* Loop over all panes and items, filling in the tree.  */
862   i = 0;
863   while (i < menu_items_used)
864     {
865       if (EQ (AREF (menu_items, i), Qnil))
866         {
867           submenu_stack[submenu_depth++] = save_wv;
868           save_wv = prev_wv;
869           prev_wv = 0;
870           first_pane = 1;
871           i++;
872         }
873       else if (EQ (AREF (menu_items, i), Qlambda))
874         {
875           prev_wv = save_wv;
876           save_wv = submenu_stack[--submenu_depth];
877           first_pane = 0;
878           i++;
879         }
880       else if (EQ (AREF (menu_items, i), Qt)
881                && submenu_depth != 0)
882         i += MENU_ITEMS_PANE_LENGTH;
883       /* Ignore a nil in the item list.
884          It's meaningful only for dialog boxes.  */
885       else if (EQ (AREF (menu_items, i), Qquote))
886         i += 1;
887       else if (EQ (AREF (menu_items, i), Qt))
888         {
889           /* Create a new pane.  */
890           Lisp_Object pane_name, prefix;
891           const char *pane_string;
893           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
894           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
896 #ifndef HAVE_MULTILINGUAL_MENU
897           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
898             {
899               pane_name = ENCODE_MENU_STRING (pane_name);
900               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
901             }
902 #endif
903           pane_string = (NILP (pane_name)
904                          ? "" : SSDATA (pane_name));
905           /* If there is just one top-level pane, put all its items directly
906              under the top-level menu.  */
907           if (menu_items_n_panes == 1)
908             pane_string = "";
910           /* If the pane has a meaningful name,
911              make the pane a top-level menu item
912              with its items as a submenu beneath it.  */
913           if (!keymaps && strcmp (pane_string, ""))
914             {
915               wv = xmalloc_widget_value ();
916               if (save_wv)
917                 save_wv->next = wv;
918               else
919                 first_wv->contents = wv;
920               wv->name = pane_string;
921               if (keymaps && !NILP (prefix))
922                 wv->name++;
923               wv->value = 0;
924               wv->enabled = 1;
925               wv->button_type = BUTTON_TYPE_NONE;
926               wv->help = Qnil;
927               save_wv = wv;
928               prev_wv = 0;
929             }
930           else if (first_pane)
931             {
932               save_wv = wv;
933               prev_wv = 0;
934             }
935           first_pane = 0;
936           i += MENU_ITEMS_PANE_LENGTH;
937         }
938       else
939         {
940           /* Create a new item within current pane.  */
941           Lisp_Object item_name, enable, descrip, def, type, selected, help;
942           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
943           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
944           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
945           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
946           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
947           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
948           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
950 #ifndef HAVE_MULTILINGUAL_MENU
951           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
952             {
953               item_name = ENCODE_MENU_STRING (item_name);
954               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
955             }
957           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
958             {
959               descrip = ENCODE_MENU_STRING (descrip);
960               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
961             }
962 #endif /* not HAVE_MULTILINGUAL_MENU */
964           wv = xmalloc_widget_value ();
965           if (prev_wv)
966             prev_wv->next = wv;
967           else
968             save_wv->contents = wv;
969           wv->name = SSDATA (item_name);
970           if (!NILP (descrip))
971             wv->key = SSDATA (descrip);
972           wv->value = 0;
973           /* If this item has a null value,
974              make the call_data null so that it won't display a box
975              when the mouse is on it.  */
976           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
977           wv->enabled = !NILP (enable);
979           if (NILP (type))
980             wv->button_type = BUTTON_TYPE_NONE;
981           else if (EQ (type, QCtoggle))
982             wv->button_type = BUTTON_TYPE_TOGGLE;
983           else if (EQ (type, QCradio))
984             wv->button_type = BUTTON_TYPE_RADIO;
985           else
986             emacs_abort ();
988           wv->selected = !NILP (selected);
990           if (! STRINGP (help))
991             help = Qnil;
993           wv->help = help;
995           prev_wv = wv;
997           i += MENU_ITEMS_ITEM_LENGTH;
998         }
999     }
1000   }
1001 #endif
1003   if (!NILP (title))
1004     {
1005       widget_value *wv_title = xmalloc_widget_value ();
1006       widget_value *wv_sep = xmalloc_widget_value ();
1008       /* Maybe replace this separator with a bitmap or owner-draw item
1009          so that it looks better.  Having two separators looks odd.  */
1010       wv_sep->name = "--";
1011       wv_sep->next = first_wv->contents;
1012       wv_sep->help = Qnil;
1014 #ifndef HAVE_MULTILINGUAL_MENU
1015       if (STRING_MULTIBYTE (title))
1016         title = ENCODE_MENU_STRING (title);
1017 #endif
1019       wv_title->name = SSDATA (title);
1020       wv_title->enabled = NO;
1021       wv_title->button_type = BUTTON_TYPE_NONE;
1022       wv_title->help = Qnil;
1023       wv_title->next = wv_sep;
1024       first_wv->contents = wv_title;
1025     }
1027   pmenu = [[EmacsMenu alloc] initWithTitle:
1028                                [NSString stringWithUTF8String: SSDATA (title)]];
1029   [pmenu fillWithWidgetValue: first_wv->contents];
1030   free_menubar_widget_value_tree (first_wv);
1031   unbind_to (specpdl_count, Qnil);
1033   popup_activated_flag = 1;
1034   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1035   popup_activated_flag = 0;
1036   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1038   return tem;
1042 /* ==========================================================================
1044     Toolbar: externally-called functions
1046    ========================================================================== */
1048 void
1049 free_frame_tool_bar (struct frame *f)
1050 /* --------------------------------------------------------------------------
1051     Under NS we just hide the toolbar until it might be needed again.
1052    -------------------------------------------------------------------------- */
1054   block_input ();
1055   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1056   FRAME_TOOLBAR_HEIGHT (f) = 0;
1057   unblock_input ();
1060 void
1061 update_frame_tool_bar (struct frame *f)
1062 /* --------------------------------------------------------------------------
1063     Update toolbar contents
1064    -------------------------------------------------------------------------- */
1066   int i, k = 0;
1067   EmacsView *view = FRAME_NS_VIEW (f);
1068   NSWindow *window = [view window];
1069   EmacsToolbar *toolbar = [view toolbar];
1071   block_input ();
1073 #ifdef NS_IMPL_COCOA
1074   [toolbar clearActive];
1075 #else
1076   [toolbar clearAll];
1077 #endif
1079   /* update EmacsToolbar as in GtkUtils, build items list */
1080   for (i = 0; i < f->n_tool_bar_items; ++i)
1081     {
1082 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1083                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1085       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1086       int idx;
1087       ptrdiff_t img_id;
1088       struct image *img;
1089       Lisp_Object image;
1090       Lisp_Object helpObj;
1091       const char *helpText;
1093       /* Check if this is a separator.  */
1094       if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1095         {
1096           /* Skip separators.  Newer OSX don't show them, and on GNUStep they
1097              are wide as a button, thus overflowing the toolbar most of
1098              the time.  */
1099           continue;
1100         }
1102       /* If image is a vector, choose the image according to the
1103          button state.  */
1104       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1105       if (VECTORP (image))
1106         {
1107           /* NS toolbar auto-computes disabled and selected images */
1108           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1109           eassert (ASIZE (image) >= idx);
1110           image = AREF (image, idx);
1111         }
1112       else
1113         {
1114           idx = -1;
1115         }
1116       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1117       if (NILP (helpObj))
1118         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1119       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1121       /* Ignore invalid image specifications.  */
1122       if (!valid_image_p (image))
1123         {
1124           /* Don't log anything, GNUS makes invalid images all the time.  */
1125           continue;
1126         }
1128       img_id = lookup_image (f, image);
1129       img = IMAGE_FROM_ID (f, img_id);
1130       prepare_image_for_display (f, img);
1132       if (img->load_failed_p || img->pixmap == nil)
1133         {
1134           NSLog (@"Could not prepare toolbar image for display.");
1135           continue;
1136         }
1138       [toolbar addDisplayItemWithImage: img->pixmap
1139                                    idx: k++
1140                                    tag: i
1141                               helpText: helpText
1142                                enabled: enabled_p];
1143 #undef TOOLPROP
1144     }
1146   if (![toolbar isVisible])
1147       [toolbar setVisible: YES];
1149 #ifdef NS_IMPL_COCOA
1150   if ([toolbar changed])
1151     {
1152       /* inform app that toolbar has changed */
1153       NSDictionary *dict = [toolbar configurationDictionary];
1154       NSMutableDictionary *newDict = [dict mutableCopy];
1155       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1156       id key;
1157       while ((key = [keys nextObject]) != nil)
1158         {
1159           NSObject *val = [dict objectForKey: key];
1160           if ([val isKindOfClass: [NSArray class]])
1161             {
1162               [newDict setObject:
1163                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1164                           forKey: key];
1165               break;
1166             }
1167         }
1168       [toolbar setConfigurationFromDictionary: newDict];
1169       [newDict release];
1170     }
1171 #endif
1173   FRAME_TOOLBAR_HEIGHT (f) =
1174     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1175     - FRAME_NS_TITLEBAR_HEIGHT (f);
1176     if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1177       FRAME_TOOLBAR_HEIGHT (f) = 0;
1178     unblock_input ();
1182 /* ==========================================================================
1184     Toolbar: class implementation
1186    ========================================================================== */
1188 @implementation EmacsToolbar
1190 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1192   self = [super initWithIdentifier: identifier];
1193   emacsView = view;
1194   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1195   [self setSizeMode: NSToolbarSizeModeSmall];
1196   [self setDelegate: self];
1197   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1198   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1199   prevIdentifiers = nil;
1200   prevEnablement = enablement = 0L;
1201   return self;
1204 - (void)dealloc
1206   [prevIdentifiers release];
1207   [activeIdentifiers release];
1208   [identifierToItem release];
1209   [super dealloc];
1212 - (void) clearActive
1214   [prevIdentifiers release];
1215   prevIdentifiers = [activeIdentifiers copy];
1216   [activeIdentifiers removeAllObjects];
1217   prevEnablement = enablement;
1218   enablement = 0L;
1221 - (void) clearAll
1223   [self clearActive];
1224   while ([[self items] count] > 0)
1225     [self removeItemAtIndex: 0];
1228 - (BOOL) changed
1230   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1231     enablement == prevEnablement ? NO : YES;
1234 - (void) addDisplayItemWithImage: (EmacsImage *)img
1235                              idx: (int)idx
1236                              tag: (int)tag
1237                         helpText: (const char *)help
1238                          enabled: (BOOL)enabled
1240   /* 1) come up w/identifier */
1241   NSString *identifier
1242       = [NSString stringWithFormat: @"%u", [img hash]];
1243   [activeIdentifiers addObject: identifier];
1245   /* 2) create / reuse item */
1246   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1247   if (item == nil)
1248     {
1249       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1250                autorelease];
1251       [item setImage: img];
1252       [item setToolTip: [NSString stringWithUTF8String: help]];
1253       [item setTarget: emacsView];
1254       [item setAction: @selector (toolbarClicked:)];
1255       [identifierToItem setObject: item forKey: identifier];
1256     }
1258 #ifdef NS_IMPL_GNUSTEP
1259   [self insertItemWithItemIdentifier: identifier atIndex: idx];
1260 #endif
1262   [item setTag: tag];
1263   [item setEnabled: enabled];
1265   /* 3) update state */
1266   enablement = (enablement << 1) | (enabled == YES);
1269 /* This overrides super's implementation, which automatically sets
1270    all items to enabled state (for some reason). */
1271 - (void)validateVisibleItems
1276 /* delegate methods */
1278 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1279       itemForItemIdentifier: (NSString *)itemIdentifier
1280   willBeInsertedIntoToolbar: (BOOL)flag
1282   /* look up NSToolbarItem by identifier and return... */
1283   return [identifierToItem objectForKey: itemIdentifier];
1286 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1288   /* return entire set.. */
1289   return activeIdentifiers;
1292 /* for configuration palette (not yet supported) */
1293 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1295   /* return entire set... */
1296   return activeIdentifiers;
1297   //return [identifierToItem allKeys];
1300 /* optional and unneeded */
1301 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1302 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1303 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1305 @end  /* EmacsToolbar */
1309 /* ==========================================================================
1311     Tooltip: class implementation
1313    ========================================================================== */
1315 /* Needed because NeXTstep does not provide enough control over tooltip
1316    display. */
1317 @implementation EmacsTooltip
1319 - init
1321   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1322                                             blue: 0.792 alpha: 0.95];
1323   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1324   NSFont *sfont = [font screenFont];
1325   int height = [sfont ascender] - [sfont descender];
1326 /*[font boundingRectForFont].size.height; */
1327   NSRect r = NSMakeRect (0, 0, 100, height+6);
1329   textField = [[NSTextField alloc] initWithFrame: r];
1330   [textField setFont: font];
1331   [textField setBackgroundColor: col];
1333   [textField setEditable: NO];
1334   [textField setSelectable: NO];
1335   [textField setBordered: NO];
1336   [textField setBezeled: NO];
1337   [textField setDrawsBackground: YES];
1339   win = [[NSWindow alloc]
1340             initWithContentRect: [textField frame]
1341                       styleMask: 0
1342                         backing: NSBackingStoreBuffered
1343                           defer: YES];
1344   [win setHasShadow: YES];
1345   [win setReleasedWhenClosed: NO];
1346   [win setDelegate: self];
1347   [[win contentView] addSubview: textField];
1348 /*  [win setBackgroundColor: col]; */
1349   [win setOpaque: NO];
1351   return self;
1354 - (void) dealloc
1356   [win close];
1357   [win release];
1358   [textField release];
1359   [super dealloc];
1362 - (void) setText: (char *)text
1364   NSString *str = [NSString stringWithUTF8String: text];
1365   NSRect r  = [textField frame];
1366   NSSize tooltipDims;
1368   [textField setStringValue: str];
1369   tooltipDims = [[textField cell] cellSize];
1371   r.size.width = tooltipDims.width;
1372   r.size.height = tooltipDims.height;
1373   [textField setFrame: r];
1376 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1378   NSRect wr = [win frame];
1380   wr.origin = NSMakePoint (x, y);
1381   wr.size = [textField frame].size;
1383   [win setFrame: wr display: YES];
1384   [win setLevel: NSPopUpMenuWindowLevel];
1385   [win orderFront: self];
1386   [win display];
1387   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1388                                          selector: @selector (hide)
1389                                          userInfo: nil repeats: NO];
1390   [timer retain];
1393 - (void) hide
1395   [win close];
1396   if (timer != nil)
1397     {
1398       if ([timer isValid])
1399         [timer invalidate];
1400       [timer release];
1401       timer = nil;
1402     }
1405 - (BOOL) isActive
1407   return timer != nil;
1410 - (NSRect) frame
1412   return [textField frame];
1415 @end  /* EmacsTooltip */
1419 /* ==========================================================================
1421     Popup Dialog: implementing functions
1423    ========================================================================== */
1425 struct Popdown_data
1427   NSAutoreleasePool *pool;
1428   EmacsDialogPanel *dialog;
1431 static void
1432 pop_down_menu (void *arg)
1434   struct Popdown_data *unwind_data = arg;
1436   block_input ();
1437   if (popup_activated_flag)
1438     {
1439       EmacsDialogPanel *panel = unwind_data->dialog;
1440       popup_activated_flag = 0;
1441       [panel close];
1442       [unwind_data->pool release];
1443       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1444     }
1446   xfree (unwind_data);
1447   unblock_input ();
1451 Lisp_Object
1452 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1454   id dialog;
1455   Lisp_Object window, tem, title;
1456   struct frame *f;
1457   NSPoint p;
1458   BOOL isQ;
1459   NSAutoreleasePool *pool;
1461   NSTRACE (x-popup-dialog);
1463   isQ = NILP (header);
1465   if (EQ (position, Qt)
1466       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1467                                || EQ (XCAR (position), Qtool_bar))))
1468     {
1469       window = selected_window;
1470     }
1471   else if (CONSP (position))
1472     {
1473       Lisp_Object tem;
1474       tem = Fcar (position);
1475       if (XTYPE (tem) == Lisp_Cons)
1476         window = Fcar (Fcdr (position));
1477       else
1478         {
1479           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1480           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1481         }
1482     }
1483   else if (WINDOWP (position) || FRAMEP (position))
1484     {
1485       window = position;
1486     }
1487   else
1488     window = Qnil;
1490   if (FRAMEP (window))
1491     f = XFRAME (window);
1492   else if (WINDOWP (window))
1493     {
1494       CHECK_LIVE_WINDOW (window);
1495       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1496     }
1497   else
1498     CHECK_WINDOW (window);
1500   check_window_system (f);
1502   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1503   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1505   title = Fcar (contents);
1506   CHECK_STRING (title);
1508   if (NILP (Fcar (Fcdr (contents))))
1509     /* No buttons specified, add an "Ok" button so users can pop down
1510        the dialog.  */
1511     contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1513   block_input ();
1514   pool = [[NSAutoreleasePool alloc] init];
1515   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1516                                            isQuestion: isQ];
1518   {
1519     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1520     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1522     unwind_data->pool = pool;
1523     unwind_data->dialog = dialog;
1525     record_unwind_protect_ptr (pop_down_menu, unwind_data);
1526     popup_activated_flag = 1;
1527     tem = [dialog runDialogAt: p];
1528     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1529   }
1531   unblock_input ();
1533   return tem;
1537 /* ==========================================================================
1539     Popup Dialog: class implementation
1541    ========================================================================== */
1543 @interface FlippedView : NSView
1546 @end
1548 @implementation FlippedView
1549 - (BOOL)isFlipped
1551   return YES;
1553 @end
1555 @implementation EmacsDialogPanel
1557 #define SPACER          8.0
1558 #define ICONSIZE        64.0
1559 #define TEXTHEIGHT      20.0
1560 #define MINCELLWIDTH    90.0
1562 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1563               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1565   NSSize spacing = {SPACER, SPACER};
1566   NSRect area;
1567   id cell;
1568   NSImageView *imgView;
1569   FlippedView *contentView;
1570   NSImage *img;
1572   dialog_return   = Qundefined;
1573   button_values   = NULL;
1574   area.origin.x   = 3*SPACER;
1575   area.origin.y   = 2*SPACER;
1576   area.size.width = ICONSIZE;
1577   area.size.height= ICONSIZE;
1578   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1579   [img setScalesWhenResized: YES];
1580   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1581   imgView = [[NSImageView alloc] initWithFrame: area];
1582   [imgView setImage: img];
1583   [imgView setEditable: NO];
1584   [img autorelease];
1585   [imgView autorelease];
1587   aStyle = NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask;
1588   flag = YES;
1589   rows = 0;
1590   cols = 1;
1591   [super initWithContentRect: contentRect styleMask: aStyle
1592                      backing: backingType defer: flag];
1593   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1594   [contentView autorelease];
1596   [self setContentView: contentView];
1598   [[self contentView] setAutoresizesSubviews: YES];
1600   [[self contentView] addSubview: imgView];
1601   [self setTitle: @""];
1603   area.origin.x   += ICONSIZE+2*SPACER;
1604 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1605   area.size.width = 400;
1606   area.size.height= TEXTHEIGHT;
1607   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1608   [[self contentView] addSubview: command];
1609   [command setStringValue: ns_app_name];
1610   [command setDrawsBackground: NO];
1611   [command setBezeled: NO];
1612   [command setSelectable: NO];
1613   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1615 /*  area.origin.x   = ICONSIZE+2*SPACER;
1616   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1617   area.size.width = 400;
1618   area.size.height= 2;
1619   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1620   [[self contentView] addSubview: tem];
1621   [tem setTitlePosition: NSNoTitle];
1622   [tem setAutoresizingMask: NSViewWidthSizable];*/
1624 /*  area.origin.x = ICONSIZE+2*SPACER; */
1625   area.origin.y += TEXTHEIGHT+SPACER;
1626   area.size.width = 400;
1627   area.size.height= TEXTHEIGHT;
1628   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1629   [[self contentView] addSubview: title];
1630   [title setDrawsBackground: NO];
1631   [title setBezeled: NO];
1632   [title setSelectable: NO];
1633   [title setFont: [NSFont systemFontOfSize: 11.0]];
1635   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1636   [cell setBordered: NO];
1637   [cell setEnabled: NO];
1638   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1639   [cell setBezelStyle: NSRoundedBezelStyle];
1641   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1642                                       mode: NSHighlightModeMatrix
1643                                  prototype: cell
1644                               numberOfRows: 0
1645                            numberOfColumns: 1];
1646   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1647                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1648   [matrix setIntercellSpacing: spacing];
1649   [matrix autorelease];
1651   [[self contentView] addSubview: matrix];
1652   [self setOneShot: YES];
1653   [self setReleasedWhenClosed: YES];
1654   [self setHidesOnDeactivate: YES];
1655   return self;
1659 - (BOOL)windowShouldClose: (id)sender
1661   window_closed = YES;
1662   [NSApp stop:self];
1663   return NO;
1666 - (void)dealloc
1668   xfree (button_values);
1669   [super dealloc];
1672 - (void)process_dialog: (Lisp_Object) list
1674   Lisp_Object item, lst = list;
1675   int row = 0;
1676   int buttons = 0, btnnr = 0;
1678   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1679     {
1680       item = XCAR (list);
1681       if (XTYPE (item) == Lisp_Cons)
1682         ++buttons;
1683     }
1685   if (buttons > 0)
1686     button_values = xmalloc (buttons * sizeof *button_values);
1688   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1689     {
1690       item = XCAR (list);
1691       if (XTYPE (item) == Lisp_String)
1692         {
1693           [self addString: SSDATA (item) row: row++];
1694         }
1695       else if (XTYPE (item) == Lisp_Cons)
1696         {
1697           button_values[btnnr] = XCDR (item);
1698           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1699           ++btnnr;
1700         }
1701       else if (NILP (item))
1702         {
1703           [self addSplit];
1704           row = 0;
1705         }
1706     }
1710 - (void)addButton: (char *)str value: (int)tag row: (int)row
1712   id cell;
1714   if (row >= rows)
1715     {
1716       [matrix addRow];
1717       rows++;
1718     }
1719   cell = [matrix cellAtRow: row column: cols-1];
1720   [cell setTarget: self];
1721   [cell setAction: @selector (clicked: )];
1722   [cell setTitle: [NSString stringWithUTF8String: str]];
1723   [cell setTag: tag];
1724   [cell setBordered: YES];
1725   [cell setEnabled: YES];
1729 - (void)addString: (char *)str row: (int)row
1731   id cell;
1733   if (row >= rows)
1734     {
1735       [matrix addRow];
1736       rows++;
1737     }
1738   cell = [matrix cellAtRow: row column: cols-1];
1739   [cell setTitle: [NSString stringWithUTF8String: str]];
1740   [cell setBordered: YES];
1741   [cell setEnabled: NO];
1745 - (void)addSplit
1747   [matrix addColumn];
1748   cols++;
1752 - (void)clicked: sender
1754   NSArray *sellist = nil;
1755   EMACS_INT seltag;
1757   sellist = [sender selectedCells];
1758   if ([sellist count] < 1)
1759     return;
1761   seltag = [[sellist objectAtIndex: 0] tag];
1762   dialog_return = button_values[seltag];
1763   [NSApp stop:self];
1767 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1769   Lisp_Object head;
1770   [super init];
1772   if (XTYPE (contents) == Lisp_Cons)
1773     {
1774       head = Fcar (contents);
1775       [self process_dialog: Fcdr (contents)];
1776     }
1777   else
1778     head = contents;
1780   if (XTYPE (head) == Lisp_String)
1781       [title setStringValue:
1782                  [NSString stringWithUTF8String: SSDATA (head)]];
1783   else if (isQ == YES)
1784       [title setStringValue: @"Question"];
1785   else
1786       [title setStringValue: @"Information"];
1788   {
1789     int i;
1790     NSRect r, s, t;
1792     if (cols == 1 && rows > 1)  /* Never told where to split */
1793       {
1794         [matrix addColumn];
1795         for (i = 0; i < rows/2; i++)
1796           {
1797             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1798                       atRow: i column: 1];
1799             [matrix removeRow: (rows+1)/2];
1800           }
1801       }
1803     [matrix sizeToFit];
1804     {
1805       NSSize csize = [matrix cellSize];
1806       if (csize.width < MINCELLWIDTH)
1807         {
1808           csize.width = MINCELLWIDTH;
1809           [matrix setCellSize: csize];
1810           [matrix sizeToCells];
1811         }
1812     }
1814     [title sizeToFit];
1815     [command sizeToFit];
1817     t = [matrix frame];
1818     r = [title frame];
1819     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1820       {
1821         t.origin.x   = r.origin.x;
1822         t.size.width = r.size.width;
1823       }
1824     r = [command frame];
1825     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1826       {
1827         t.origin.x   = r.origin.x;
1828         t.size.width = r.size.width;
1829       }
1831     r = [self frame];
1832     s = [(NSView *)[self contentView] frame];
1833     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1834     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1835     [self setFrame: r display: NO];
1836   }
1838   return self;
1843 - (void)timeout_handler: (NSTimer *)timedEntry
1845   NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1846                             location: NSMakePoint (0, 0)
1847                        modifierFlags: 0
1848                            timestamp: 0
1849                         windowNumber: [[NSApp mainWindow] windowNumber]
1850                              context: [NSApp context]
1851                              subtype: 0
1852                                data1: 0
1853                                data2: 0];
1855   timer_fired = YES;
1856   /* We use sto because stopModal/abortModal out of the main loop does not
1857      seem to work in 10.6.  But as we use stop we must send a real event so
1858      the stop is seen and acted upon.  */
1859   [NSApp stop:self];
1860   [NSApp postEvent: nxev atStart: NO];
1863 - (Lisp_Object)runDialogAt: (NSPoint)p
1865   Lisp_Object ret = Qundefined;
1867   while (popup_activated_flag)
1868     {
1869       NSTimer *tmo = nil;
1870       EMACS_TIME next_time = timer_check ();
1872       if (EMACS_TIME_VALID_P (next_time))
1873         {
1874           double time = EMACS_TIME_TO_DOUBLE (next_time);
1875           tmo = [NSTimer timerWithTimeInterval: time
1876                                         target: self
1877                                       selector: @selector (timeout_handler:)
1878                                       userInfo: 0
1879                                        repeats: NO];
1880           [[NSRunLoop currentRunLoop] addTimer: tmo
1881                                        forMode: NSModalPanelRunLoopMode];
1882         }
1883       timer_fired = NO;
1884       dialog_return = Qundefined;
1885       [NSApp runModalForWindow: self];
1886       ret = dialog_return;
1887       if (! timer_fired)
1888         {
1889           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1890           break;
1891         }
1892     }
1894   if (EQ (ret, Qundefined) && window_closed)
1895     /* Make close button pressed equivalent to C-g.  */
1896     Fsignal (Qquit, Qnil);
1898   return ret;
1901 @end
1904 /* ==========================================================================
1906     Lisp definitions
1908    ========================================================================== */
1910 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1911        doc: /* Cause the NS menu to be re-calculated.  */)
1912      (void)
1914   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1915   return Qnil;
1919 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1920        doc: /* Pop up a dialog box and return user's selection.
1921 POSITION specifies which frame to use.
1922 This is normally a mouse button event or a window or frame.
1923 If POSITION is t, it means to use the frame the mouse is on.
1924 The dialog box appears in the middle of the specified frame.
1926 CONTENTS specifies the alternatives to display in the dialog box.
1927 It is a list of the form (DIALOG ITEM1 ITEM2...).
1928 Each ITEM is a cons cell (STRING . VALUE).
1929 The return value is VALUE from the chosen item.
1931 An ITEM may also be just a string--that makes a nonselectable item.
1932 An ITEM may also be nil--that means to put all preceding items
1933 on the left of the dialog box and all following items on the right.
1934 \(By default, approximately half appear on each side.)
1936 If HEADER is non-nil, the frame title for the box is "Information",
1937 otherwise it is "Question".
1939 If the user gets rid of the dialog box without making a valid choice,
1940 for instance using the window manager, then this produces a quit and
1941 `x-popup-dialog' does not return.  */)
1942      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1944   return ns_popup_dialog (position, contents, header);
1947 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1948        doc: /* Return t if a menu or popup dialog is active.  */)
1949      (void)
1951   return popup_activated () ? Qt : Qnil;
1954 /* ==========================================================================
1956     Lisp interface declaration
1958    ========================================================================== */
1960 void
1961 syms_of_nsmenu (void)
1963 #ifndef NS_IMPL_COCOA
1964   /* Don't know how to keep track of this in Next/Open/Gnustep.  Always
1965      update menus there.  */
1966   trackingMenu = 1;
1967 #endif
1968   defsubr (&Sx_popup_dialog);
1969   defsubr (&Sns_reset_menu);
1970   defsubr (&Smenu_or_popup_active_p);
1972   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1973   staticpro (&Qdebug_on_next_call);