* todo-mode.el: Offer to convert legacy file. Update commentary.
[emacs.git] / src / nsmenu.m
blob1d3d111e9a1ace110e826b64c4900e1da21281e7
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];
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   NSArray *a = [[NSApp mainMenu] itemArray];
508   /* Update each submenu separately so ns_update_menubar doesn't reset
509      the delegate.  */
510   int i = 0;
511   while (i < [a count])
512     {
513       EmacsMenu *menu = (EmacsMenu *)[[a objectAtIndex:i] submenu];
514       const char *title = [[menu title] UTF8String];
515       if (strcmp (title, ns_get_pending_menu_title ()) == 0)
516         {
517           ns_update_menubar (f, true, menu);
518           break;
519         }
520       ++i;
521     }
522   ns_check_pending_open_menu ();
523 #endif
529 /* ==========================================================================
531     Menu: class implementation
533    ========================================================================== */
536 /* Menu that can define itself from Emacs "widget_value"s and will lazily
537    update itself when user clicked.  Based on Carbon/AppKit implementation
538    by Yamamoto Mitsuharu. */
539 @implementation EmacsMenu
541 /* override designated initializer */
542 - initWithTitle: (NSString *)title
544   if ((self = [super initWithTitle: title]))
545     [self setAutoenablesItems: NO];
546   return self;
550 /* used for top-level */
551 - initWithTitle: (NSString *)title frame: (struct frame *)f
553   [self initWithTitle: title];
554   frame = f;
555 #ifdef NS_IMPL_COCOA
556   [self setDelegate: self];
557 #endif
558   return self;
562 - (void)setFrame: (struct frame *)f
564   frame = f;
567 #ifdef NS_IMPL_COCOA
568 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
569 extern NSString *NSMenuDidBeginTrackingNotification;
570 #endif
571 #endif
573 #ifdef NS_IMPL_COCOA
574 -(void)trackingNotification:(NSNotification *)notification
576   /* Update menu in menuNeedsUpdate only while tracking menus.  */
577   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
578                   ? 1 : 0);
581 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
582 - (void)menuWillOpen:(NSMenu *)menu
584   ns_check_menu_open (menu);
586 #endif
588 #endif
590 /* delegate method called when a submenu is being opened: run a 'deep' call
591    to set_frame_menubar */
592 - (void)menuNeedsUpdate: (NSMenu *)menu
594   if (!FRAME_LIVE_P (frame))
595     return;
597   /* Cocoa/Carbon will request update on every keystroke
598      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
599      since key equivalents are handled through emacs.
600      On Leopard, even keystroke events generate SystemDefined event.
601      Third-party applications that enhance mouse / trackpad
602      interaction, or also VNC/Remote Desktop will send events
603      of type AppDefined rather than SysDefined.
604      Menus will fail to show up if they haven't been initialized.
605      AppDefined events may lack timing data.
607      Thus, we rely on the didBeginTrackingNotification notification
608      as above to indicate the need for updates.
609      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
610      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
611   */
612   if (trackingMenu == 0)
613     return;
614 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
615 #if ! defined(NS_IMPL_COCOA) || \
616   MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
617   /* Don't know how to do this for anything other than OSX >= 10.5
618      This is wrong, as it might run Lisp code in the event loop.  */
619   ns_update_menubar (frame, true, self);
620 #endif
624 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
626   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
627       && FRAME_NS_VIEW (SELECTED_FRAME ()))
628     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
629   return YES;
633 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
634    into an accelerator string.  We are only able to display a single character
635    for an accelerator, together with an optional modifier combination.  (Under
636    Carbon more control was possible, but in Cocoa multi-char strings passed to
637    NSMenuItem get ignored.  For now we try to display a super-single letter
638    combo, and return the others as strings to be appended to the item title.
639    (This is signaled by setting keyEquivModMask to 0 for now.) */
640 -(NSString *)parseKeyEquiv: (const char *)key
642   const char *tpos = key;
643   keyEquivModMask = NSCommandKeyMask;
645   if (!key || !strlen (key))
646     return @"";
648   while (*tpos == ' ' || *tpos == '(')
649     tpos++;
650   if ((*tpos == 's') && (*(tpos+1) == '-'))
651     {
652       return [NSString stringWithFormat: @"%c", tpos[2]];
653     }
654   keyEquivModMask = 0; /* signal */
655   return [NSString stringWithUTF8String: tpos];
659 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
661   NSMenuItem *item;
662   widget_value *wv = (widget_value *)wvptr;
664   if (menu_separator_name_p (wv->name))
665     {
666       item = [NSMenuItem separatorItem];
667       [self addItem: item];
668     }
669   else
670     {
671       NSString *title, *keyEq;
672       title = [NSString stringWithUTF8String: wv->name];
673       if (title == nil)
674         title = @"< ? >";  /* (get out in the open so we know about it) */
676       keyEq = [self parseKeyEquiv: wv->key];
677 #ifdef NS_IMPL_COCOA
678       /* OS X just ignores modifier strings longer than one character */
679       if (keyEquivModMask == 0)
680         title = [title stringByAppendingFormat: @" (%@)", keyEq];
681 #endif
683       item = [self addItemWithTitle: (NSString *)title
684                              action: @selector (menuDown:)
685                       keyEquivalent: keyEq];
686       [item setKeyEquivalentModifierMask: keyEquivModMask];
688       [item setEnabled: wv->enabled];
690       /* Draw radio buttons and tickboxes */
691       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
692                            wv->button_type == BUTTON_TYPE_RADIO))
693         [item setState: NSOnState];
694       else
695         [item setState: NSOffState];
697       [item setTag: (NSInteger)wv->call_data];
698     }
700   return item;
704 /* convenience */
705 -(void)clear
707   int n;
709   for (n = [self numberOfItems]-1; n >= 0; n--)
710     {
711       NSMenuItem *item = [self itemAtIndex: n];
712       NSString *title = [item title];
713       if (([title length] == 0  /* OSX 10.5 */
714            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
715            || [@"Apple" isEqualToString: title]) /* older */
716           && ![item isSeparatorItem])
717         continue;
718       [self removeItemAtIndex: n];
719     }
723 - (void)fillWithWidgetValue: (void *)wvptr
725   widget_value *wv = (widget_value *)wvptr;
727   /* clear existing contents */
728   [self setMenuChangedMessagesEnabled: NO];
729   [self clear];
731   /* add new contents */
732   for (; wv != NULL; wv = wv->next)
733     {
734       NSMenuItem *item = [self addItemWithWidgetValue: wv];
736       if (wv->contents)
737         {
738           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
740           [self setSubmenu: submenu forItem: item];
741           [submenu fillWithWidgetValue: wv->contents];
742           [submenu release];
743           [item setAction: (SEL)nil];
744         }
745     }
747   [self setMenuChangedMessagesEnabled: YES];
748 #ifdef NS_IMPL_GNUSTEP
749   if ([[self window] isVisible])
750     [self sizeToFit];
751 #endif
755 /* adds an empty submenu and returns it */
756 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
758   NSString *titleStr = [NSString stringWithUTF8String: title];
759   NSMenuItem *item = [self addItemWithTitle: titleStr
760                                      action: (SEL)nil /*@selector (menuDown:) */
761                               keyEquivalent: @""];
762   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
763   [self setSubmenu: submenu forItem: item];
764   [submenu release];
765   return submenu;
768 /* run a menu in popup mode */
769 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
770                  keymaps: (bool)keymaps
772   EmacsView *view = FRAME_NS_VIEW (f);
773   NSEvent *e, *event;
774   long retVal;
776 /*   p = [view convertPoint:p fromView: nil]; */
777   p.y = NSHeight ([view frame]) - p.y;
778   e = [[view window] currentEvent];
779    event = [NSEvent mouseEventWithType: NSRightMouseDown
780                               location: p
781                          modifierFlags: 0
782                              timestamp: [e timestamp]
783                           windowNumber: [[view window] windowNumber]
784                                context: [e context]
785                            eventNumber: 0/*[e eventNumber] */
786                             clickCount: 1
787                               pressure: 0];
789   context_menu_value = -1;
790   [NSMenu popUpContextMenu: self withEvent: event forView: view];
791   retVal = context_menu_value;
792   context_menu_value = 0;
793   return retVal > 0
794       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
795       : Qnil;
798 @end  /* EmacsMenu */
802 /* ==========================================================================
804     Context Menu: implementing functions
806    ========================================================================== */
808 Lisp_Object
809 ns_menu_show (FRAME_PTR f, int x, int y, bool for_click, bool keymaps,
810               Lisp_Object title, const char **error)
812   EmacsMenu *pmenu;
813   NSPoint p;
814   Lisp_Object tem;
815   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
816   widget_value *wv, *first_wv = 0;
818   p.x = x; p.y = y;
820   /* now parse stage 2 as in ns_update_menubar */
821   wv = xmalloc_widget_value ();
822   wv->name = "contextmenu";
823   wv->value = 0;
824   wv->enabled = 1;
825   wv->button_type = BUTTON_TYPE_NONE;
826   wv->help = Qnil;
827   first_wv = wv;
829 #if 0
830   /* FIXME: a couple of one-line differences prevent reuse */
831   wv = digest_single_submenu (0, menu_items_used, 0);
832 #else
833   {
834   widget_value *save_wv = 0, *prev_wv = 0;
835   widget_value **submenu_stack
836     = alloca (menu_items_used * sizeof *submenu_stack);
837 /*   Lisp_Object *subprefix_stack
838        = alloca (menu_items_used * sizeof *subprefix_stack); */
839   int submenu_depth = 0;
840   int first_pane = 1;
841   int i;
843   /* Loop over all panes and items, filling in the tree.  */
844   i = 0;
845   while (i < menu_items_used)
846     {
847       if (EQ (AREF (menu_items, i), Qnil))
848         {
849           submenu_stack[submenu_depth++] = save_wv;
850           save_wv = prev_wv;
851           prev_wv = 0;
852           first_pane = 1;
853           i++;
854         }
855       else if (EQ (AREF (menu_items, i), Qlambda))
856         {
857           prev_wv = save_wv;
858           save_wv = submenu_stack[--submenu_depth];
859           first_pane = 0;
860           i++;
861         }
862       else if (EQ (AREF (menu_items, i), Qt)
863                && submenu_depth != 0)
864         i += MENU_ITEMS_PANE_LENGTH;
865       /* Ignore a nil in the item list.
866          It's meaningful only for dialog boxes.  */
867       else if (EQ (AREF (menu_items, i), Qquote))
868         i += 1;
869       else if (EQ (AREF (menu_items, i), Qt))
870         {
871           /* Create a new pane.  */
872           Lisp_Object pane_name, prefix;
873           const char *pane_string;
875           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
876           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
878 #ifndef HAVE_MULTILINGUAL_MENU
879           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
880             {
881               pane_name = ENCODE_MENU_STRING (pane_name);
882               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
883             }
884 #endif
885           pane_string = (NILP (pane_name)
886                          ? "" : SSDATA (pane_name));
887           /* If there is just one top-level pane, put all its items directly
888              under the top-level menu.  */
889           if (menu_items_n_panes == 1)
890             pane_string = "";
892           /* If the pane has a meaningful name,
893              make the pane a top-level menu item
894              with its items as a submenu beneath it.  */
895           if (!keymaps && strcmp (pane_string, ""))
896             {
897               wv = xmalloc_widget_value ();
898               if (save_wv)
899                 save_wv->next = wv;
900               else
901                 first_wv->contents = wv;
902               wv->name = pane_string;
903               if (keymaps && !NILP (prefix))
904                 wv->name++;
905               wv->value = 0;
906               wv->enabled = 1;
907               wv->button_type = BUTTON_TYPE_NONE;
908               wv->help = Qnil;
909               save_wv = wv;
910               prev_wv = 0;
911             }
912           else if (first_pane)
913             {
914               save_wv = wv;
915               prev_wv = 0;
916             }
917           first_pane = 0;
918           i += MENU_ITEMS_PANE_LENGTH;
919         }
920       else
921         {
922           /* Create a new item within current pane.  */
923           Lisp_Object item_name, enable, descrip, def, type, selected, help;
924           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
925           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
926           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
927           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
928           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
929           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
930           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
932 #ifndef HAVE_MULTILINGUAL_MENU
933           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
934             {
935               item_name = ENCODE_MENU_STRING (item_name);
936               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
937             }
939           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
940             {
941               descrip = ENCODE_MENU_STRING (descrip);
942               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
943             }
944 #endif /* not HAVE_MULTILINGUAL_MENU */
946           wv = xmalloc_widget_value ();
947           if (prev_wv)
948             prev_wv->next = wv;
949           else
950             save_wv->contents = wv;
951           wv->name = SSDATA (item_name);
952           if (!NILP (descrip))
953             wv->key = SSDATA (descrip);
954           wv->value = 0;
955           /* If this item has a null value,
956              make the call_data null so that it won't display a box
957              when the mouse is on it.  */
958           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
959           wv->enabled = !NILP (enable);
961           if (NILP (type))
962             wv->button_type = BUTTON_TYPE_NONE;
963           else if (EQ (type, QCtoggle))
964             wv->button_type = BUTTON_TYPE_TOGGLE;
965           else if (EQ (type, QCradio))
966             wv->button_type = BUTTON_TYPE_RADIO;
967           else
968             emacs_abort ();
970           wv->selected = !NILP (selected);
972           if (! STRINGP (help))
973             help = Qnil;
975           wv->help = help;
977           prev_wv = wv;
979           i += MENU_ITEMS_ITEM_LENGTH;
980         }
981     }
982   }
983 #endif
985   if (!NILP (title))
986     {
987       widget_value *wv_title = xmalloc_widget_value ();
988       widget_value *wv_sep = xmalloc_widget_value ();
990       /* Maybe replace this separator with a bitmap or owner-draw item
991          so that it looks better.  Having two separators looks odd.  */
992       wv_sep->name = "--";
993       wv_sep->next = first_wv->contents;
994       wv_sep->help = Qnil;
996 #ifndef HAVE_MULTILINGUAL_MENU
997       if (STRING_MULTIBYTE (title))
998         title = ENCODE_MENU_STRING (title);
999 #endif
1001       wv_title->name = SSDATA (title);
1002       wv_title->enabled = NO;
1003       wv_title->button_type = BUTTON_TYPE_NONE;
1004       wv_title->help = Qnil;
1005       wv_title->next = wv_sep;
1006       first_wv->contents = wv_title;
1007     }
1009   pmenu = [[EmacsMenu alloc] initWithTitle:
1010                                [NSString stringWithUTF8String: SSDATA (title)]];
1011   [pmenu fillWithWidgetValue: first_wv->contents];
1012   free_menubar_widget_value_tree (first_wv);
1013   unbind_to (specpdl_count, Qnil);
1015   popup_activated_flag = 1;
1016   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1017   popup_activated_flag = 0;
1018   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1020   return tem;
1024 /* ==========================================================================
1026     Toolbar: externally-called functions
1028    ========================================================================== */
1030 void
1031 free_frame_tool_bar (FRAME_PTR f)
1032 /* --------------------------------------------------------------------------
1033     Under NS we just hide the toolbar until it might be needed again.
1034    -------------------------------------------------------------------------- */
1036   block_input ();
1037   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1038   FRAME_TOOLBAR_HEIGHT (f) = 0;
1039   unblock_input ();
1042 void
1043 update_frame_tool_bar (FRAME_PTR f)
1044 /* --------------------------------------------------------------------------
1045     Update toolbar contents
1046    -------------------------------------------------------------------------- */
1048   int i, k = 0;
1049   EmacsView *view = FRAME_NS_VIEW (f);
1050   NSWindow *window = [view window];
1051   EmacsToolbar *toolbar = [view toolbar];
1053   block_input ();
1055 #ifdef NS_IMPL_COCOA
1056   [toolbar clearActive];
1057 #else
1058   [toolbar clearAll];
1059 #endif
1061   /* update EmacsToolbar as in GtkUtils, build items list */
1062   for (i = 0; i < f->n_tool_bar_items; ++i)
1063     {
1064 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1065                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1067       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1068       int idx;
1069       ptrdiff_t img_id;
1070       struct image *img;
1071       Lisp_Object image;
1072       Lisp_Object helpObj;
1073       const char *helpText;
1075       /* Check if this is a separator.  */
1076       if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1077         {
1078           /* Skip separators.  Newer OSX don't show them, and on GNUStep they
1079              are wide as a button, thus overflowing the toolbar most of
1080              the time.  */
1081           continue;
1082         }
1084       /* If image is a vector, choose the image according to the
1085          button state.  */
1086       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1087       if (VECTORP (image))
1088         {
1089           /* NS toolbar auto-computes disabled and selected images */
1090           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1091           eassert (ASIZE (image) >= idx);
1092           image = AREF (image, idx);
1093         }
1094       else
1095         {
1096           idx = -1;
1097         }
1098       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1099       if (NILP (helpObj))
1100         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1101       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1103       /* Ignore invalid image specifications.  */
1104       if (!valid_image_p (image))
1105         {
1106           /* Don't log anything, GNUS makes invalid images all the time.  */
1107           continue;
1108         }
1110       img_id = lookup_image (f, image);
1111       img = IMAGE_FROM_ID (f, img_id);
1112       prepare_image_for_display (f, img);
1114       if (img->load_failed_p || img->pixmap == nil)
1115         {
1116           NSLog (@"Could not prepare toolbar image for display.");
1117           continue;
1118         }
1120       [toolbar addDisplayItemWithImage: img->pixmap
1121                                    idx: k++
1122                                    tag: i
1123                               helpText: helpText
1124                                enabled: enabled_p];
1125 #undef TOOLPROP
1126     }
1128   if (![toolbar isVisible])
1129       [toolbar setVisible: YES];
1131 #ifdef NS_IMPL_COCOA
1132   if ([toolbar changed])
1133     {
1134       /* inform app that toolbar has changed */
1135       NSDictionary *dict = [toolbar configurationDictionary];
1136       NSMutableDictionary *newDict = [dict mutableCopy];
1137       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1138       id key;
1139       while ((key = [keys nextObject]) != nil)
1140         {
1141           NSObject *val = [dict objectForKey: key];
1142           if ([val isKindOfClass: [NSArray class]])
1143             {
1144               [newDict setObject:
1145                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1146                           forKey: key];
1147               break;
1148             }
1149         }
1150       [toolbar setConfigurationFromDictionary: newDict];
1151       [newDict release];
1152     }
1153 #endif
1155   FRAME_TOOLBAR_HEIGHT (f) =
1156     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1157     - FRAME_NS_TITLEBAR_HEIGHT (f);
1158     if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1159       FRAME_TOOLBAR_HEIGHT (f) = 0;
1160     unblock_input ();
1164 /* ==========================================================================
1166     Toolbar: class implementation
1168    ========================================================================== */
1170 @implementation EmacsToolbar
1172 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1174   self = [super initWithIdentifier: identifier];
1175   emacsView = view;
1176   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1177   [self setSizeMode: NSToolbarSizeModeSmall];
1178   [self setDelegate: self];
1179   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1180   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1181   prevIdentifiers = nil;
1182   prevEnablement = enablement = 0L;
1183   return self;
1186 - (void)dealloc
1188   [prevIdentifiers release];
1189   [activeIdentifiers release];
1190   [identifierToItem release];
1191   [super dealloc];
1194 - (void) clearActive
1196   [prevIdentifiers release];
1197   prevIdentifiers = [activeIdentifiers copy];
1198   [activeIdentifiers removeAllObjects];
1199   prevEnablement = enablement;
1200   enablement = 0L;
1203 - (void) clearAll
1205   [self clearActive];
1206   while ([[self items] count] > 0)
1207     [self removeItemAtIndex: 0];
1210 - (BOOL) changed
1212   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1213     enablement == prevEnablement ? NO : YES;
1216 - (void) addDisplayItemWithImage: (EmacsImage *)img
1217                              idx: (int)idx
1218                              tag: (int)tag
1219                         helpText: (const char *)help
1220                          enabled: (BOOL)enabled
1222   /* 1) come up w/identifier */
1223   NSString *identifier
1224       = [NSString stringWithFormat: @"%u", [img hash]];
1225   [activeIdentifiers addObject: identifier];
1227   /* 2) create / reuse item */
1228   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1229   if (item == nil)
1230     {
1231       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1232                autorelease];
1233       [item setImage: img];
1234       [item setToolTip: [NSString stringWithUTF8String: help]];
1235       [item setTarget: emacsView];
1236       [item setAction: @selector (toolbarClicked:)];
1237       [identifierToItem setObject: item forKey: identifier];
1238     }
1240 #ifdef NS_IMPL_GNUSTEP
1241   [self insertItemWithItemIdentifier: identifier atIndex: idx];
1242 #endif
1243   
1244   [item setTag: tag];
1245   [item setEnabled: enabled];
1247   /* 3) update state */
1248   enablement = (enablement << 1) | (enabled == YES);
1251 /* This overrides super's implementation, which automatically sets
1252    all items to enabled state (for some reason). */
1253 - (void)validateVisibleItems
1258 /* delegate methods */
1260 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1261       itemForItemIdentifier: (NSString *)itemIdentifier
1262   willBeInsertedIntoToolbar: (BOOL)flag
1264   /* look up NSToolbarItem by identifier and return... */
1265   return [identifierToItem objectForKey: itemIdentifier];
1268 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1270   /* return entire set.. */
1271   return activeIdentifiers;
1274 /* for configuration palette (not yet supported) */
1275 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1277   /* return entire set... */
1278   return activeIdentifiers;
1279   //return [identifierToItem allKeys];
1282 /* optional and unneeded */
1283 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1284 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1285 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1287 @end  /* EmacsToolbar */
1291 /* ==========================================================================
1293     Tooltip: class implementation
1295    ========================================================================== */
1297 /* Needed because NeXTstep does not provide enough control over tooltip
1298    display. */
1299 @implementation EmacsTooltip
1301 - init
1303   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1304                                             blue: 0.792 alpha: 0.95];
1305   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1306   NSFont *sfont = [font screenFont];
1307   int height = [sfont ascender] - [sfont descender];
1308 /*[font boundingRectForFont].size.height; */
1309   NSRect r = NSMakeRect (0, 0, 100, height+6);
1311   textField = [[NSTextField alloc] initWithFrame: r];
1312   [textField setFont: font];
1313   [textField setBackgroundColor: col];
1315   [textField setEditable: NO];
1316   [textField setSelectable: NO];
1317   [textField setBordered: NO];
1318   [textField setBezeled: NO];
1319   [textField setDrawsBackground: YES];
1321   win = [[NSWindow alloc]
1322             initWithContentRect: [textField frame]
1323                       styleMask: 0
1324                         backing: NSBackingStoreBuffered
1325                           defer: YES];
1326   [win setHasShadow: YES];
1327   [win setReleasedWhenClosed: NO];
1328   [win setDelegate: self];
1329   [[win contentView] addSubview: textField];
1330 /*  [win setBackgroundColor: col]; */
1331   [win setOpaque: NO];
1333   return self;
1336 - (void) dealloc
1338   [win close];
1339   [win release];
1340   [textField release];
1341   [super dealloc];
1344 - (void) setText: (char *)text
1346   NSString *str = [NSString stringWithUTF8String: text];
1347   NSRect r  = [textField frame];
1348   NSSize tooltipDims;
1350   [textField setStringValue: str];
1351   tooltipDims = [[textField cell] cellSize];
1353   r.size.width = tooltipDims.width;
1354   r.size.height = tooltipDims.height;
1355   [textField setFrame: r];
1358 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1360   NSRect wr = [win frame];
1362   wr.origin = NSMakePoint (x, y);
1363   wr.size = [textField frame].size;
1365   [win setFrame: wr display: YES];
1366   [win setLevel: NSPopUpMenuWindowLevel];
1367   [win orderFront: self];
1368   [win display];
1369   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1370                                          selector: @selector (hide)
1371                                          userInfo: nil repeats: NO];
1372   [timer retain];
1375 - (void) hide
1377   [win close];
1378   if (timer != nil)
1379     {
1380       if ([timer isValid])
1381         [timer invalidate];
1382       [timer release];
1383       timer = nil;
1384     }
1387 - (BOOL) isActive
1389   return timer != nil;
1392 - (NSRect) frame
1394   return [textField frame];
1397 @end  /* EmacsTooltip */
1401 /* ==========================================================================
1403     Popup Dialog: implementing functions
1405    ========================================================================== */
1407 struct Popdown_data
1409   NSAutoreleasePool *pool;
1410   EmacsDialogPanel *dialog;
1413 static Lisp_Object
1414 pop_down_menu (Lisp_Object arg)
1416   struct Popdown_data *unwind_data = XSAVE_POINTER (arg, 0);
1418   block_input ();
1419   if (popup_activated_flag)
1420     {
1421       EmacsDialogPanel *panel = unwind_data->dialog;
1422       popup_activated_flag = 0;
1423       [panel close];
1424       [unwind_data->pool release];
1425       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1426     }
1428   xfree (unwind_data);
1429   unblock_input ();
1431   return Qnil;
1435 Lisp_Object
1436 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1438   id dialog;
1439   Lisp_Object window, tem, title;
1440   struct frame *f;
1441   NSPoint p;
1442   BOOL isQ;
1443   NSAutoreleasePool *pool;
1445   NSTRACE (x-popup-dialog);
1447   isQ = NILP (header);
1449   if (EQ (position, Qt)
1450       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1451                                || EQ (XCAR (position), Qtool_bar))))
1452     {
1453       window = selected_window;
1454     }
1455   else if (CONSP (position))
1456     {
1457       Lisp_Object tem;
1458       tem = Fcar (position);
1459       if (XTYPE (tem) == Lisp_Cons)
1460         window = Fcar (Fcdr (position));
1461       else
1462         {
1463           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1464           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1465         }
1466     }
1467   else if (WINDOWP (position) || FRAMEP (position))
1468     {
1469       window = position;
1470     }
1471   else
1472     window = Qnil;
1474   if (FRAMEP (window))
1475     f = XFRAME (window);
1476   else if (WINDOWP (window))
1477     {
1478       CHECK_LIVE_WINDOW (window);
1479       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1480     }
1481   else
1482     CHECK_WINDOW (window);
1484   check_window_system (f);
1486   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1487   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1489   title = Fcar (contents);
1490   CHECK_STRING (title);
1492   if (NILP (Fcar (Fcdr (contents))))
1493     /* No buttons specified, add an "Ok" button so users can pop down
1494        the dialog.  */
1495     contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1497   block_input ();
1498   pool = [[NSAutoreleasePool alloc] init];
1499   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1500                                            isQuestion: isQ];
1502   {
1503     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1504     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1506     unwind_data->pool = pool;
1507     unwind_data->dialog = dialog;
1509     record_unwind_protect (pop_down_menu, make_save_pointer (unwind_data));
1510     popup_activated_flag = 1;
1511     tem = [dialog runDialogAt: p];
1512     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1513   }
1515   unblock_input ();
1517   return tem;
1521 /* ==========================================================================
1523     Popup Dialog: class implementation
1525    ========================================================================== */
1527 @interface FlippedView : NSView
1530 @end
1532 @implementation FlippedView
1533 - (BOOL)isFlipped
1535   return YES;
1537 @end
1539 @implementation EmacsDialogPanel
1541 #define SPACER          8.0
1542 #define ICONSIZE        64.0
1543 #define TEXTHEIGHT      20.0
1544 #define MINCELLWIDTH    90.0
1546 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1547               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1549   NSSize spacing = {SPACER, SPACER};
1550   NSRect area;
1551   id cell;
1552   NSImageView *imgView;
1553   FlippedView *contentView;
1554   NSImage *img;
1556   dialog_return   = Qundefined;
1557   button_values   = NULL;
1558   area.origin.x   = 3*SPACER;
1559   area.origin.y   = 2*SPACER;
1560   area.size.width = ICONSIZE;
1561   area.size.height= ICONSIZE;
1562   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1563   [img setScalesWhenResized: YES];
1564   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1565   imgView = [[NSImageView alloc] initWithFrame: area];
1566   [imgView setImage: img];
1567   [imgView setEditable: NO];
1568   [img autorelease];
1569   [imgView autorelease];
1571   aStyle = NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask;
1572   flag = YES;
1573   rows = 0;
1574   cols = 1;
1575   [super initWithContentRect: contentRect styleMask: aStyle
1576                      backing: backingType defer: flag];
1577   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1578   [contentView autorelease];
1580   [self setContentView: contentView];
1582   [[self contentView] setAutoresizesSubviews: YES];
1584   [[self contentView] addSubview: imgView];
1585   [self setTitle: @""];
1587   area.origin.x   += ICONSIZE+2*SPACER;
1588 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1589   area.size.width = 400;
1590   area.size.height= TEXTHEIGHT;
1591   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1592   [[self contentView] addSubview: command];
1593   [command setStringValue: ns_app_name];
1594   [command setDrawsBackground: NO];
1595   [command setBezeled: NO];
1596   [command setSelectable: NO];
1597   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1599 /*  area.origin.x   = ICONSIZE+2*SPACER;
1600   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1601   area.size.width = 400;
1602   area.size.height= 2;
1603   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1604   [[self contentView] addSubview: tem];
1605   [tem setTitlePosition: NSNoTitle];
1606   [tem setAutoresizingMask: NSViewWidthSizable];*/
1608 /*  area.origin.x = ICONSIZE+2*SPACER; */
1609   area.origin.y += TEXTHEIGHT+SPACER;
1610   area.size.width = 400;
1611   area.size.height= TEXTHEIGHT;
1612   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1613   [[self contentView] addSubview: title];
1614   [title setDrawsBackground: NO];
1615   [title setBezeled: NO];
1616   [title setSelectable: NO];
1617   [title setFont: [NSFont systemFontOfSize: 11.0]];
1619   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1620   [cell setBordered: NO];
1621   [cell setEnabled: NO];
1622   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1623   [cell setBezelStyle: NSRoundedBezelStyle];
1625   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1626                                       mode: NSHighlightModeMatrix
1627                                  prototype: cell
1628                               numberOfRows: 0
1629                            numberOfColumns: 1];
1630   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1631                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1632   [matrix setIntercellSpacing: spacing];
1633   [matrix autorelease];
1635   [[self contentView] addSubview: matrix];
1636   [self setOneShot: YES];
1637   [self setReleasedWhenClosed: YES];
1638   [self setHidesOnDeactivate: YES];
1639   return self;
1643 - (BOOL)windowShouldClose: (id)sender
1645   window_closed = YES;
1646   [NSApp stop:self];
1647   return NO;
1650 - (void)dealloc
1652   xfree (button_values);
1653   [super dealloc];
1656 - (void)process_dialog: (Lisp_Object) list
1658   Lisp_Object item, lst = list;
1659   int row = 0;
1660   int buttons = 0, btnnr = 0;
1662   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1663     {
1664       item = XCAR (list);
1665       if (XTYPE (item) == Lisp_Cons)
1666         ++buttons;
1667     }
1669   if (buttons > 0)
1670     button_values = (Lisp_Object *) xmalloc (buttons * sizeof (*button_values));
1672   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1673     {
1674       item = XCAR (list);
1675       if (XTYPE (item) == Lisp_String)
1676         {
1677           [self addString: SSDATA (item) row: row++];
1678         }
1679       else if (XTYPE (item) == Lisp_Cons)
1680         {
1681           button_values[btnnr] = XCDR (item);
1682           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1683           ++btnnr;
1684         }
1685       else if (NILP (item))
1686         {
1687           [self addSplit];
1688           row = 0;
1689         }
1690     }
1694 - (void)addButton: (char *)str value: (int)tag row: (int)row
1696   id cell;
1698   if (row >= rows)
1699     {
1700       [matrix addRow];
1701       rows++;
1702     }
1703   cell = [matrix cellAtRow: row column: cols-1];
1704   [cell setTarget: self];
1705   [cell setAction: @selector (clicked: )];
1706   [cell setTitle: [NSString stringWithUTF8String: str]];
1707   [cell setTag: tag];
1708   [cell setBordered: YES];
1709   [cell setEnabled: YES];
1713 - (void)addString: (char *)str row: (int)row
1715   id cell;
1717   if (row >= rows)
1718     {
1719       [matrix addRow];
1720       rows++;
1721     }
1722   cell = [matrix cellAtRow: row column: cols-1];
1723   [cell setTitle: [NSString stringWithUTF8String: str]];
1724   [cell setBordered: YES];
1725   [cell setEnabled: NO];
1729 - (void)addSplit
1731   [matrix addColumn];
1732   cols++;
1736 - (void)clicked: sender
1738   NSArray *sellist = nil;
1739   EMACS_INT seltag;
1741   sellist = [sender selectedCells];
1742   if ([sellist count] < 1)
1743     return;
1745   seltag = [[sellist objectAtIndex: 0] tag];
1746   dialog_return = button_values[seltag];
1747   [NSApp stop:self];
1751 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1753   Lisp_Object head;
1754   [super init];
1756   if (XTYPE (contents) == Lisp_Cons)
1757     {
1758       head = Fcar (contents);
1759       [self process_dialog: Fcdr (contents)];
1760     }
1761   else
1762     head = contents;
1764   if (XTYPE (head) == Lisp_String)
1765       [title setStringValue:
1766                  [NSString stringWithUTF8String: SSDATA (head)]];
1767   else if (isQ == YES)
1768       [title setStringValue: @"Question"];
1769   else
1770       [title setStringValue: @"Information"];
1772   {
1773     int i;
1774     NSRect r, s, t;
1776     if (cols == 1 && rows > 1)  /* Never told where to split */
1777       {
1778         [matrix addColumn];
1779         for (i = 0; i < rows/2; i++)
1780           {
1781             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1782                       atRow: i column: 1];
1783             [matrix removeRow: (rows+1)/2];
1784           }
1785       }
1787     [matrix sizeToFit];
1788     {
1789       NSSize csize = [matrix cellSize];
1790       if (csize.width < MINCELLWIDTH)
1791         {
1792           csize.width = MINCELLWIDTH;
1793           [matrix setCellSize: csize];
1794           [matrix sizeToCells];
1795         }
1796     }
1798     [title sizeToFit];
1799     [command sizeToFit];
1801     t = [matrix frame];
1802     r = [title frame];
1803     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1804       {
1805         t.origin.x   = r.origin.x;
1806         t.size.width = r.size.width;
1807       }
1808     r = [command frame];
1809     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1810       {
1811         t.origin.x   = r.origin.x;
1812         t.size.width = r.size.width;
1813       }
1815     r = [self frame];
1816     s = [(NSView *)[self contentView] frame];
1817     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1818     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1819     [self setFrame: r display: NO];
1820   }
1822   return self;
1827 - (void)timeout_handler: (NSTimer *)timedEntry
1829   NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1830                             location: NSMakePoint (0, 0)
1831                        modifierFlags: 0
1832                            timestamp: 0
1833                         windowNumber: [[NSApp mainWindow] windowNumber]
1834                              context: [NSApp context]
1835                              subtype: 0
1836                                data1: 0
1837                                data2: 0];
1839   timer_fired = YES;
1840   /* We use sto because stopModal/abortModal out of the main loop does not
1841      seem to work in 10.6.  But as we use stop we must send a real event so
1842      the stop is seen and acted upon.  */
1843   [NSApp stop:self];
1844   [NSApp postEvent: nxev atStart: NO];
1847 - (Lisp_Object)runDialogAt: (NSPoint)p
1849   Lisp_Object ret = Qundefined;
1851   while (popup_activated_flag)
1852     {
1853       NSTimer *tmo = nil;
1854       EMACS_TIME next_time = timer_check ();
1856       if (EMACS_TIME_VALID_P (next_time))
1857         {
1858           double time = EMACS_TIME_TO_DOUBLE (next_time);
1859           tmo = [NSTimer timerWithTimeInterval: time
1860                                         target: self
1861                                       selector: @selector (timeout_handler:)
1862                                       userInfo: 0
1863                                        repeats: NO];
1864           [[NSRunLoop currentRunLoop] addTimer: tmo
1865                                        forMode: NSModalPanelRunLoopMode];
1866         }
1867       timer_fired = NO;
1868       dialog_return = Qundefined;
1869       [NSApp runModalForWindow: self];
1870       ret = dialog_return;
1871       if (! timer_fired)
1872         {
1873           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1874           break;
1875         }
1876     }
1878   if (EQ (ret, Qundefined) && window_closed)
1879     /* Make close button pressed equivalent to C-g.  */
1880     Fsignal (Qquit, Qnil);
1882   return ret;
1885 @end
1888 /* ==========================================================================
1890     Lisp definitions
1892    ========================================================================== */
1894 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1895        doc: /* Cause the NS menu to be re-calculated.  */)
1896      (void)
1898   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1899   return Qnil;
1903 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1904        doc: /* Pop up a dialog box and return user's selection.
1905 POSITION specifies which frame to use.
1906 This is normally a mouse button event or a window or frame.
1907 If POSITION is t, it means to use the frame the mouse is on.
1908 The dialog box appears in the middle of the specified frame.
1910 CONTENTS specifies the alternatives to display in the dialog box.
1911 It is a list of the form (DIALOG ITEM1 ITEM2...).
1912 Each ITEM is a cons cell (STRING . VALUE).
1913 The return value is VALUE from the chosen item.
1915 An ITEM may also be just a string--that makes a nonselectable item.
1916 An ITEM may also be nil--that means to put all preceding items
1917 on the left of the dialog box and all following items on the right.
1918 \(By default, approximately half appear on each side.)
1920 If HEADER is non-nil, the frame title for the box is "Information",
1921 otherwise it is "Question".
1923 If the user gets rid of the dialog box without making a valid choice,
1924 for instance using the window manager, then this produces a quit and
1925 `x-popup-dialog' does not return.  */)
1926      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1928   return ns_popup_dialog (position, contents, header);
1931 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1932        doc: /* Return t if a menu or popup dialog is active.  */)
1933      (void)
1935   return popup_activated () ? Qt : Qnil;
1938 /* ==========================================================================
1940     Lisp interface declaration
1942    ========================================================================== */
1944 void
1945 syms_of_nsmenu (void)
1947 #ifndef NS_IMPL_COCOA
1948   /* Don't know how to keep track of this in Next/Open/Gnustep.  Always
1949      update menus there.  */
1950   trackingMenu = 1;
1951 #endif
1952   defsubr (&Sx_popup_dialog);
1953   defsubr (&Sns_reset_menu);
1954   defsubr (&Smenu_or_popup_active_p);
1956   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1957   staticpro (&Qdebug_on_next_call);