Do not require libXt-devel when building with gtk.
[emacs.git] / src / nsmenu.m
blob46b7400b2e41290588a1075006b59e7848510e0f
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007-2014 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 = make_widget_value ("menubar", NULL, true, Qnil);
270       wv->button_type = BUTTON_TYPE_NONE;
271       first_wv = wv;
273       for (i = 0; i < 4*n; i += 4)
274         {
275           menu_items_n_panes = submenu_n_panes[i];
276           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
277                                       submenu_top_level_items[i]);
278           if (prev_wv)
279             prev_wv->next = wv;
280           else
281             first_wv->contents = wv;
282           /* Don't set wv->name here; GC during the loop might relocate it.  */
283           wv->enabled = 1;
284           wv->button_type = BUTTON_TYPE_NONE;
285           prev_wv = wv;
286         }
288       set_buffer_internal_1 (prev);
290       /* Compare the new menu items with previous, and leave off if no change */
291       /* FIXME: following other terms here, but seems like this should be
292            done before parse stage 2 above, since its results aren't used */
293       if (previous_menu_items_used
294           && (!submenu || (submenu && submenu == last_submenu))
295           && menu_items_used == previous_menu_items_used)
296         {
297           for (i = 0; i < previous_menu_items_used; i++)
298             /* FIXME: this ALWAYS fails on Buffers menu items.. something
299                  about their strings causes them to change every time, so we
300                  double-check failures */
301             if (!EQ (previous_items[i], AREF (menu_items, i)))
302               if (!(STRINGP (previous_items[i])
303                     && STRINGP (AREF (menu_items, i))
304                     && !strcmp (SSDATA (previous_items[i]),
305                                 SSDATA (AREF (menu_items, i)))))
306                   break;
307           if (i == previous_menu_items_used)
308             {
309               /* No change.. */
311 #if NSMENUPROFILE
312               ftime (&tb);
313               t += 1000*tb.time+tb.millitm;
314               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
315 #endif
317               free_menubar_widget_value_tree (first_wv);
318               discard_menu_items ();
319               unbind_to (specpdl_count, Qnil);
320               [pool release];
321               unblock_input ();
322               return;
323             }
324         }
325       /* The menu items are different, so store them in the frame */
326       /* FIXME: this is not correct for single-submenu case */
327       fset_menu_bar_vector (f, menu_items);
328       f->menu_bar_items_used = menu_items_used;
330       /* Calls restore_menu_items, etc., as they were outside */
331       unbind_to (specpdl_count, Qnil);
333       /* Parse stage 2a: now GC cannot happen during the lifetime of the
334          widget_value, so it's safe to store data from a Lisp_String */
335       wv = first_wv->contents;
336       for (i = 0; i < ASIZE (items); i += 4)
337         {
338           Lisp_Object string;
339           string = AREF (items, i + 1);
340           if (NILP (string))
341             break;
343           wv->name = SSDATA (string);
344           update_submenu_strings (wv->contents);
345           wv = wv->next;
346         }
348       /* Now, update the NS menu; if we have a submenu, use that, otherwise
349          create a new menu for each sub and fill it. */
350       if (submenu)
351         {
352           const char *submenuTitle = [[submenu title] UTF8String];
353           for (wv = first_wv->contents; wv; wv = wv->next)
354             {
355               if (!strcmp (submenuTitle, wv->name))
356                 {
357                   [submenu fillWithWidgetValue: wv->contents];
358                   last_submenu = submenu;
359                   break;
360                 }
361             }
362         }
363       else
364         {
365           [menu fillWithWidgetValue: first_wv->contents frame: f];
366         }
368     }
369   else
370     {
371       static int n_previous_strings = 0;
372       static char previous_strings[100][10];
373       static struct frame *last_f = NULL;
374       int n;
375       Lisp_Object string;
377       wv = make_widget_value ("menubar", NULL, true, Qnil);
378       wv->button_type = BUTTON_TYPE_NONE;
379       first_wv = wv;
381       /* Make widget-value tree w/ just the top level menu bar strings */
382       items = FRAME_MENU_BAR_ITEMS (f);
383       if (NILP (items))
384         {
385           free_menubar_widget_value_tree (first_wv);
386           [pool release];
387           unblock_input ();
388           return;
389         }
392       /* check if no change.. this mechanism is a bit rough, but ready */
393       n = ASIZE (items) / 4;
394       if (f == last_f && n_previous_strings == n)
395         {
396           for (i = 0; i<n; i++)
397             {
398               string = AREF (items, 4*i+1);
400               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
401                 continue;
402               if (NILP (string))
403                 {
404                   if (previous_strings[i][0])
405                     break;
406                   else
407                     continue;
408                 }
409               else if (memcmp (previous_strings[i], SDATA (string),
410                           min (10, SBYTES (string) + 1)))
411                 break;
412             }
414           if (i == n)
415             {
416               free_menubar_widget_value_tree (first_wv);
417               [pool release];
418               unblock_input ();
419               return;
420             }
421         }
423       [menu clear];
424       for (i = 0; i < ASIZE (items); i += 4)
425         {
426           string = AREF (items, i + 1);
427           if (NILP (string))
428             break;
430           if (n < 100)
431             memcpy (previous_strings[i/4], SDATA (string),
432                     min (10, SBYTES (string) + 1));
434           wv = make_widget_value (SSDATA (string), NULL, true, Qnil);
435           wv->button_type = BUTTON_TYPE_NONE;
436           wv->call_data = (void *) (intptr_t) (-1);
438 #ifdef NS_IMPL_COCOA
439           /* we'll update the real copy under app menu when time comes */
440           if (!strcmp ("Services", wv->name))
441             {
442               /* but we need to make sure it will update on demand */
443               [svcsMenu setFrame: f];
444             }
445           else
446 #endif
447           [menu addSubmenuWithTitle: wv->name forFrame: f];
449           if (prev_wv)
450             prev_wv->next = wv;
451           else
452             first_wv->contents = wv;
453           prev_wv = wv;
454         }
456       last_f = f;
457       if (n < 100)
458         n_previous_strings = n;
459       else
460         n_previous_strings = 0;
462     }
463   free_menubar_widget_value_tree (first_wv);
466 #if NSMENUPROFILE
467   ftime (&tb);
468   t += 1000*tb.time+tb.millitm;
469   fprintf (stderr, "Menu update took %ld msec.\n", t);
470 #endif
472   /* set main menu */
473   if (needsSet)
474     [NSApp setMainMenu: menu];
476   [pool release];
477   unblock_input ();
482 /* Main emacs core entry point for menubar menus: called to indicate that the
483    frame's menus have changed, and the *step representation should be updated
484    from Lisp. */
485 void
486 set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
488   ns_update_menubar (f, deep_p, nil);
491 void
492 x_activate_menubar (struct frame *f)
494 #ifdef NS_IMPL_COCOA
495 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
496   ns_update_menubar (f, true, nil);
497   ns_check_pending_open_menu ();
498 #endif
499 #endif
505 /* ==========================================================================
507     Menu: class implementation
509    ========================================================================== */
512 /* Menu that can define itself from Emacs "widget_value"s and will lazily
513    update itself when user clicked.  Based on Carbon/AppKit implementation
514    by Yamamoto Mitsuharu. */
515 @implementation EmacsMenu
517 /* override designated initializer */
518 - initWithTitle: (NSString *)title
520   frame = 0;
521   if ((self = [super initWithTitle: title]))
522     [self setAutoenablesItems: NO];
523   return self;
527 /* used for top-level */
528 - initWithTitle: (NSString *)title frame: (struct frame *)f
530   [self initWithTitle: title];
531   frame = f;
532 #ifdef NS_IMPL_COCOA
533   [self setDelegate: self];
534 #endif
535   return self;
539 - (void)setFrame: (struct frame *)f
541   frame = f;
544 #ifdef NS_IMPL_COCOA
545 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
546 extern NSString *NSMenuDidBeginTrackingNotification;
547 #endif
548 #endif
550 #ifdef NS_IMPL_COCOA
551 -(void)trackingNotification:(NSNotification *)notification
553   /* Update menu in menuNeedsUpdate only while tracking menus.  */
554   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
555                   ? 1 : 0);
556 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
557   if (! trackingMenu) ns_check_menu_open (nil);
558 #endif
561 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
562 - (void)menuWillOpen:(NSMenu *)menu
564   ++trackingMenu;
566 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
567   // On 10.6 we get repeated calls, only the one for NSSystemDefined is "real".
568   if ([[NSApp currentEvent] type] != NSSystemDefined) return;
569 #endif
571   /* When dragging from one menu to another, we get willOpen followed by didClose,
572      i.e. trackingMenu == 3 in willOpen and then 2 after didClose.
573      We have updated all menus, so avoid doing it when trackingMenu == 3.  */
574   if (trackingMenu == 2)
575     ns_check_menu_open (menu);
578 - (void)menuDidClose:(NSMenu *)menu
580   --trackingMenu;
582 #endif /* OSX >= 10.5 */
584 #endif /* NS_IMPL_COCOA */
586 /* delegate method called when a submenu is being opened: run a 'deep' call
587    to set_frame_menubar */
588 - (void)menuNeedsUpdate: (NSMenu *)menu
590   if (!FRAME_LIVE_P (frame))
591     return;
593   /* Cocoa/Carbon will request update on every keystroke
594      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
595      since key equivalents are handled through emacs.
596      On Leopard, even keystroke events generate SystemDefined event.
597      Third-party applications that enhance mouse / trackpad
598      interaction, or also VNC/Remote Desktop will send events
599      of type AppDefined rather than SysDefined.
600      Menus will fail to show up if they haven't been initialized.
601      AppDefined events may lack timing data.
603      Thus, we rely on the didBeginTrackingNotification notification
604      as above to indicate the need for updates.
605      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
606      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
607   */
608   if (trackingMenu == 0)
609     return;
610 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
611 #if (! defined (NS_IMPL_COCOA) \
612      || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5)
613   /* Don't know how to do this for anything other than OSX >= 10.5
614      This is wrong, as it might run Lisp code in the event loop.  */
615   ns_update_menubar (frame, true, self);
616 #endif
620 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
622   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
623       && FRAME_NS_VIEW (SELECTED_FRAME ()))
624     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
625   return YES;
629 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
630    into an accelerator string.  We are only able to display a single character
631    for an accelerator, together with an optional modifier combination.  (Under
632    Carbon more control was possible, but in Cocoa multi-char strings passed to
633    NSMenuItem get ignored.  For now we try to display a super-single letter
634    combo, and return the others as strings to be appended to the item title.
635    (This is signaled by setting keyEquivModMask to 0 for now.) */
636 -(NSString *)parseKeyEquiv: (const char *)key
638   const char *tpos = key;
639   keyEquivModMask = NSCommandKeyMask;
641   if (!key || !strlen (key))
642     return @"";
644   while (*tpos == ' ' || *tpos == '(')
645     tpos++;
646   if ((*tpos == 's') && (*(tpos+1) == '-'))
647     {
648       return [NSString stringWithFormat: @"%c", tpos[2]];
649     }
650   keyEquivModMask = 0; /* signal */
651   return [NSString stringWithUTF8String: tpos];
655 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
657   NSMenuItem *item;
658   widget_value *wv = (widget_value *)wvptr;
660   if (menu_separator_name_p (wv->name))
661     {
662       item = [NSMenuItem separatorItem];
663       [self addItem: item];
664     }
665   else
666     {
667       NSString *title, *keyEq;
668       title = [NSString stringWithUTF8String: wv->name];
669       if (title == nil)
670         title = @"< ? >";  /* (get out in the open so we know about it) */
672       keyEq = [self parseKeyEquiv: wv->key];
673 #ifdef NS_IMPL_COCOA
674       /* OS X just ignores modifier strings longer than one character */
675       if (keyEquivModMask == 0)
676         title = [title stringByAppendingFormat: @" (%@)", keyEq];
677 #endif
679       item = [self addItemWithTitle: (NSString *)title
680                              action: @selector (menuDown:)
681                       keyEquivalent: keyEq];
682       [item setKeyEquivalentModifierMask: keyEquivModMask];
684       [item setEnabled: wv->enabled];
686       /* Draw radio buttons and tickboxes */
687       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
688                            wv->button_type == BUTTON_TYPE_RADIO))
689         [item setState: NSOnState];
690       else
691         [item setState: NSOffState];
693       [item setTag: (NSInteger)wv->call_data];
694     }
696   return item;
700 /* convenience */
701 -(void)clear
703   int n;
705   for (n = [self numberOfItems]-1; n >= 0; n--)
706     {
707       NSMenuItem *item = [self itemAtIndex: n];
708       NSString *title = [item title];
709       if (([title length] == 0  /* OSX 10.5 */
710            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
711            || [@"Apple" isEqualToString: title]) /* older */
712           && ![item isSeparatorItem])
713         continue;
714       [self removeItemAtIndex: n];
715     }
719 - (void)fillWithWidgetValue: (void *)wvptr
721   [self fillWithWidgetValue: wvptr frame: (struct frame *)nil];
724 - (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f
726   widget_value *wv = (widget_value *)wvptr;
728   /* clear existing contents */
729   [self setMenuChangedMessagesEnabled: NO];
730   [self clear];
732   /* add new contents */
733   for (; wv != NULL; wv = wv->next)
734     {
735       NSMenuItem *item = [self addItemWithWidgetValue: wv];
737       if (wv->contents)
738         {
739           EmacsMenu *submenu;
741           if (f)
742             submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f];
743           else
744             submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
746           [self setSubmenu: submenu forItem: item];
747           [submenu fillWithWidgetValue: wv->contents];
748           [submenu release];
749           [item setAction: (SEL)nil];
750         }
751     }
753   [self setMenuChangedMessagesEnabled: YES];
754 #ifdef NS_IMPL_GNUSTEP
755   if ([[self window] isVisible])
756     [self sizeToFit];
757 #endif
761 /* adds an empty submenu and returns it */
762 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
764   NSString *titleStr = [NSString stringWithUTF8String: title];
765   NSMenuItem *item = [self addItemWithTitle: titleStr
766                                      action: (SEL)nil /*@selector (menuDown:) */
767                               keyEquivalent: @""];
768   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
769   [self setSubmenu: submenu forItem: item];
770   [submenu release];
771   return submenu;
774 /* run a menu in popup mode */
775 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
776                  keymaps: (bool)keymaps
778   EmacsView *view = FRAME_NS_VIEW (f);
779   NSEvent *e, *event;
780   long retVal;
782 /*   p = [view convertPoint:p fromView: nil]; */
783   p.y = NSHeight ([view frame]) - p.y;
784   e = [[view window] currentEvent];
785    event = [NSEvent mouseEventWithType: NSRightMouseDown
786                               location: p
787                          modifierFlags: 0
788                              timestamp: [e timestamp]
789                           windowNumber: [[view window] windowNumber]
790                                context: [e context]
791                            eventNumber: 0/*[e eventNumber] */
792                             clickCount: 1
793                               pressure: 0];
795   context_menu_value = -1;
796   [NSMenu popUpContextMenu: self withEvent: event forView: view];
797   retVal = context_menu_value;
798   context_menu_value = 0;
799   return retVal > 0
800       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
801       : Qnil;
804 @end  /* EmacsMenu */
808 /* ==========================================================================
810     Context Menu: implementing functions
812    ========================================================================== */
814 Lisp_Object
815 ns_menu_show (struct frame *f, int x, int y, bool for_click, bool keymaps,
816               Lisp_Object title, const char **error)
818   EmacsMenu *pmenu;
819   NSPoint p;
820   Lisp_Object tem;
821   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
822   widget_value *wv, *first_wv = 0;
824   block_input ();
826   p.x = x; p.y = y;
828   /* now parse stage 2 as in ns_update_menubar */
829   wv = make_widget_value ("contextmenu", NULL, true, Qnil);
830   wv->button_type = BUTTON_TYPE_NONE;
831   first_wv = wv;
833 #if 0
834   /* FIXME: a couple of one-line differences prevent reuse */
835   wv = digest_single_submenu (0, menu_items_used, 0);
836 #else
837   {
838   widget_value *save_wv = 0, *prev_wv = 0;
839   widget_value **submenu_stack
840     = alloca (menu_items_used * sizeof *submenu_stack);
841 /*   Lisp_Object *subprefix_stack
842        = alloca (menu_items_used * sizeof *subprefix_stack); */
843   int submenu_depth = 0;
844   int first_pane = 1;
845   int i;
847   /* Loop over all panes and items, filling in the tree.  */
848   i = 0;
849   while (i < menu_items_used)
850     {
851       if (EQ (AREF (menu_items, i), Qnil))
852         {
853           submenu_stack[submenu_depth++] = save_wv;
854           save_wv = prev_wv;
855           prev_wv = 0;
856           first_pane = 1;
857           i++;
858         }
859       else if (EQ (AREF (menu_items, i), Qlambda))
860         {
861           prev_wv = save_wv;
862           save_wv = submenu_stack[--submenu_depth];
863           first_pane = 0;
864           i++;
865         }
866       else if (EQ (AREF (menu_items, i), Qt)
867                && submenu_depth != 0)
868         i += MENU_ITEMS_PANE_LENGTH;
869       /* Ignore a nil in the item list.
870          It's meaningful only for dialog boxes.  */
871       else if (EQ (AREF (menu_items, i), Qquote))
872         i += 1;
873       else if (EQ (AREF (menu_items, i), Qt))
874         {
875           /* Create a new pane.  */
876           Lisp_Object pane_name, prefix;
877           const char *pane_string;
879           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
880           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
882 #ifndef HAVE_MULTILINGUAL_MENU
883           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
884             {
885               pane_name = ENCODE_MENU_STRING (pane_name);
886               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
887             }
888 #endif
889           pane_string = (NILP (pane_name)
890                          ? "" : SSDATA (pane_name));
891           /* If there is just one top-level pane, put all its items directly
892              under the top-level menu.  */
893           if (menu_items_n_panes == 1)
894             pane_string = "";
896           /* If the pane has a meaningful name,
897              make the pane a top-level menu item
898              with its items as a submenu beneath it.  */
899           if (!keymaps && strcmp (pane_string, ""))
900             {
901               wv = make_widget_value (pane_string, NULL, true, Qnil);
902               if (save_wv)
903                 save_wv->next = wv;
904               else
905                 first_wv->contents = wv;
906               if (keymaps && !NILP (prefix))
907                 wv->name++;
908               wv->button_type = BUTTON_TYPE_NONE;
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 = make_widget_value (SSDATA (item_name), NULL, !NILP (enable),
947                                   STRINGP (help) ? help : Qnil);
948           if (prev_wv)
949             prev_wv->next = wv;
950           else
951             save_wv->contents = wv;
952           if (!NILP (descrip))
953             wv->key = SSDATA (descrip);
954           /* If this item has a null value,
955              make the call_data null so that it won't display a box
956              when the mouse is on it.  */
957           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
959           if (NILP (type))
960             wv->button_type = BUTTON_TYPE_NONE;
961           else if (EQ (type, QCtoggle))
962             wv->button_type = BUTTON_TYPE_TOGGLE;
963           else if (EQ (type, QCradio))
964             wv->button_type = BUTTON_TYPE_RADIO;
965           else
966             emacs_abort ();
968           wv->selected = !NILP (selected);
970           prev_wv = wv;
972           i += MENU_ITEMS_ITEM_LENGTH;
973         }
974     }
975   }
976 #endif
978   if (!NILP (title))
979     {
980       widget_value *wv_title;
981       widget_value *wv_sep = make_widget_value ("--", NULL, false, Qnil);
983       /* Maybe replace this separator with a bitmap or owner-draw item
984          so that it looks better.  Having two separators looks odd.  */
985       wv_sep->next = first_wv->contents;
987 #ifndef HAVE_MULTILINGUAL_MENU
988       if (STRING_MULTIBYTE (title))
989         title = ENCODE_MENU_STRING (title);
990 #endif
991       wv_title = make_widget_value (SSDATA (title), NULL, false, Qnil);
992       wv_title->button_type = BUTTON_TYPE_NONE;
993       wv_title->next = wv_sep;
994       first_wv->contents = wv_title;
995     }
997   pmenu = [[EmacsMenu alloc] initWithTitle:
998                                [NSString stringWithUTF8String: SSDATA (title)]];
999   [pmenu fillWithWidgetValue: first_wv->contents];
1000   free_menubar_widget_value_tree (first_wv);
1001   unbind_to (specpdl_count, Qnil);
1003   popup_activated_flag = 1;
1004   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1005   popup_activated_flag = 0;
1006   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1008   unblock_input ();
1009   return tem;
1013 /* ==========================================================================
1015     Toolbar: externally-called functions
1017    ========================================================================== */
1019 void
1020 free_frame_tool_bar (struct frame *f)
1021 /* --------------------------------------------------------------------------
1022     Under NS we just hide the toolbar until it might be needed again.
1023    -------------------------------------------------------------------------- */
1025   EmacsView *view = FRAME_NS_VIEW (f);
1026   block_input ();
1027   view->wait_for_tool_bar = NO;
1028   [[view toolbar] setVisible: NO];
1029   FRAME_TOOLBAR_HEIGHT (f) = 0;
1030   unblock_input ();
1033 void
1034 update_frame_tool_bar (struct frame *f)
1035 /* --------------------------------------------------------------------------
1036     Update toolbar contents
1037    -------------------------------------------------------------------------- */
1039   int i, k = 0;
1040   EmacsView *view = FRAME_NS_VIEW (f);
1041   NSWindow *window = [view window];
1042   EmacsToolbar *toolbar = [view toolbar];
1044   if (view == nil || toolbar == nil) return;
1045   block_input ();
1047 #ifdef NS_IMPL_COCOA
1048   [toolbar clearActive];
1049 #else
1050   [toolbar clearAll];
1051 #endif
1053   /* update EmacsToolbar as in GtkUtils, build items list */
1054   for (i = 0; i < f->n_tool_bar_items; ++i)
1055     {
1056 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1057                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1059       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1060       int idx;
1061       ptrdiff_t img_id;
1062       struct image *img;
1063       Lisp_Object image;
1064       Lisp_Object helpObj;
1065       const char *helpText;
1067       /* Check if this is a separator.  */
1068       if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1069         {
1070           /* Skip separators.  Newer OSX don't show them, and on GNUstep they
1071              are wide as a button, thus overflowing the toolbar most of
1072              the time.  */
1073           continue;
1074         }
1076       /* If image is a vector, choose the image according to the
1077          button state.  */
1078       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1079       if (VECTORP (image))
1080         {
1081           /* NS toolbar auto-computes disabled and selected images */
1082           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1083           eassert (ASIZE (image) >= idx);
1084           image = AREF (image, idx);
1085         }
1086       else
1087         {
1088           idx = -1;
1089         }
1090       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1091       if (NILP (helpObj))
1092         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1093       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1095       /* Ignore invalid image specifications.  */
1096       if (!valid_image_p (image))
1097         {
1098           /* Don't log anything, GNUS makes invalid images all the time.  */
1099           continue;
1100         }
1102       img_id = lookup_image (f, image);
1103       img = IMAGE_FROM_ID (f, img_id);
1104       prepare_image_for_display (f, img);
1106       if (img->load_failed_p || img->pixmap == nil)
1107         {
1108           NSLog (@"Could not prepare toolbar image for display.");
1109           continue;
1110         }
1112       [toolbar addDisplayItemWithImage: img->pixmap
1113                                    idx: k++
1114                                    tag: i
1115                               helpText: helpText
1116                                enabled: enabled_p];
1117 #undef TOOLPROP
1118     }
1120   if (![toolbar isVisible])
1121       [toolbar setVisible: YES];
1123 #ifdef NS_IMPL_COCOA
1124   if ([toolbar changed])
1125     {
1126       /* inform app that toolbar has changed */
1127       NSDictionary *dict = [toolbar configurationDictionary];
1128       NSMutableDictionary *newDict = [dict mutableCopy];
1129       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1130       id key;
1131       while ((key = [keys nextObject]) != nil)
1132         {
1133           NSObject *val = [dict objectForKey: key];
1134           if ([val isKindOfClass: [NSArray class]])
1135             {
1136               [newDict setObject:
1137                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1138                           forKey: key];
1139               break;
1140             }
1141         }
1142       [toolbar setConfigurationFromDictionary: newDict];
1143       [newDict release];
1144     }
1145 #endif
1147   FRAME_TOOLBAR_HEIGHT (f) =
1148     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1149     - FRAME_NS_TITLEBAR_HEIGHT (f);
1150   if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1151     FRAME_TOOLBAR_HEIGHT (f) = 0;
1153   if (view->wait_for_tool_bar && FRAME_TOOLBAR_HEIGHT (f) > 0)
1154       [view setNeedsDisplay: YES];
1156   unblock_input ();
1160 /* ==========================================================================
1162     Toolbar: class implementation
1164    ========================================================================== */
1166 @implementation EmacsToolbar
1168 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1170   self = [super initWithIdentifier: identifier];
1171   emacsView = view;
1172   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1173   [self setSizeMode: NSToolbarSizeModeSmall];
1174   [self setDelegate: self];
1175   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1176   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1177   prevIdentifiers = nil;
1178   prevEnablement = enablement = 0L;
1179   return self;
1182 - (void)dealloc
1184   [prevIdentifiers release];
1185   [activeIdentifiers release];
1186   [identifierToItem release];
1187   [super dealloc];
1190 - (void) clearActive
1192   [prevIdentifiers release];
1193   prevIdentifiers = [activeIdentifiers copy];
1194   [activeIdentifiers removeAllObjects];
1195   prevEnablement = enablement;
1196   enablement = 0L;
1199 - (void) clearAll
1201   [self clearActive];
1202   while ([[self items] count] > 0)
1203     [self removeItemAtIndex: 0];
1206 - (BOOL) changed
1208   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1209     enablement == prevEnablement ? NO : YES;
1212 - (void) addDisplayItemWithImage: (EmacsImage *)img
1213                              idx: (int)idx
1214                              tag: (int)tag
1215                         helpText: (const char *)help
1216                          enabled: (BOOL)enabled
1218   /* 1) come up w/identifier */
1219   NSString *identifier
1220     = [NSString stringWithFormat: @"%lu", (unsigned long)[img hash]];
1221   [activeIdentifiers addObject: identifier];
1223   /* 2) create / reuse item */
1224   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1225   if (item == nil)
1226     {
1227       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1228                autorelease];
1229       [item setImage: img];
1230       [item setToolTip: [NSString stringWithUTF8String: help]];
1231       [item setTarget: emacsView];
1232       [item setAction: @selector (toolbarClicked:)];
1233       [identifierToItem setObject: item forKey: identifier];
1234     }
1236 #ifdef NS_IMPL_GNUSTEP
1237   [self insertItemWithItemIdentifier: identifier atIndex: idx];
1238 #endif
1240   [item setTag: tag];
1241   [item setEnabled: enabled];
1243   /* 3) update state */
1244   enablement = (enablement << 1) | (enabled == YES);
1247 /* This overrides super's implementation, which automatically sets
1248    all items to enabled state (for some reason). */
1249 - (void)validateVisibleItems
1254 /* delegate methods */
1256 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1257       itemForItemIdentifier: (NSString *)itemIdentifier
1258   willBeInsertedIntoToolbar: (BOOL)flag
1260   /* look up NSToolbarItem by identifier and return... */
1261   return [identifierToItem objectForKey: itemIdentifier];
1264 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1266   /* return entire set.. */
1267   return activeIdentifiers;
1270 /* for configuration palette (not yet supported) */
1271 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1273   /* return entire set... */
1274   return activeIdentifiers;
1275   //return [identifierToItem allKeys];
1278 /* optional and unneeded */
1279 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1280 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1281 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1283 @end  /* EmacsToolbar */
1287 /* ==========================================================================
1289     Tooltip: class implementation
1291    ========================================================================== */
1293 /* Needed because NeXTstep does not provide enough control over tooltip
1294    display. */
1295 @implementation EmacsTooltip
1297 - init
1299   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1300                                             blue: 0.792 alpha: 0.95];
1301   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1302   NSFont *sfont = [font screenFont];
1303   int height = [sfont ascender] - [sfont descender];
1304 /*[font boundingRectForFont].size.height; */
1305   NSRect r = NSMakeRect (0, 0, 100, height+6);
1307   textField = [[NSTextField alloc] initWithFrame: r];
1308   [textField setFont: font];
1309   [textField setBackgroundColor: col];
1311   [textField setEditable: NO];
1312   [textField setSelectable: NO];
1313   [textField setBordered: NO];
1314   [textField setBezeled: NO];
1315   [textField setDrawsBackground: YES];
1317   win = [[NSWindow alloc]
1318             initWithContentRect: [textField frame]
1319                       styleMask: 0
1320                         backing: NSBackingStoreBuffered
1321                           defer: YES];
1322   [win setHasShadow: YES];
1323   [win setReleasedWhenClosed: NO];
1324   [win setDelegate: self];
1325   [[win contentView] addSubview: textField];
1326 /*  [win setBackgroundColor: col]; */
1327   [win setOpaque: NO];
1329   return self;
1332 - (void) dealloc
1334   [win close];
1335   [win release];
1336   [textField release];
1337   [super dealloc];
1340 - (void) setText: (char *)text
1342   NSString *str = [NSString stringWithUTF8String: text];
1343   NSRect r  = [textField frame];
1344   NSSize tooltipDims;
1346   [textField setStringValue: str];
1347   tooltipDims = [[textField cell] cellSize];
1349   r.size.width = tooltipDims.width;
1350   r.size.height = tooltipDims.height;
1351   [textField setFrame: r];
1354 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1356   NSRect wr = [win frame];
1358   wr.origin = NSMakePoint (x, y);
1359   wr.size = [textField frame].size;
1361   [win setFrame: wr display: YES];
1362   [win setLevel: NSPopUpMenuWindowLevel];
1363   [win orderFront: self];
1364   [win display];
1365   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1366                                          selector: @selector (hide)
1367                                          userInfo: nil repeats: NO];
1368   [timer retain];
1371 - (void) hide
1373   [win close];
1374   if (timer != nil)
1375     {
1376       if ([timer isValid])
1377         [timer invalidate];
1378       [timer release];
1379       timer = nil;
1380     }
1383 - (BOOL) isActive
1385   return timer != nil;
1388 - (NSRect) frame
1390   return [textField frame];
1393 @end  /* EmacsTooltip */
1397 /* ==========================================================================
1399     Popup Dialog: implementing functions
1401    ========================================================================== */
1403 struct Popdown_data
1405   NSAutoreleasePool *pool;
1406   EmacsDialogPanel *dialog;
1409 static void
1410 pop_down_menu (void *arg)
1412   struct Popdown_data *unwind_data = arg;
1414   block_input ();
1415   if (popup_activated_flag)
1416     {
1417       EmacsDialogPanel *panel = unwind_data->dialog;
1418       popup_activated_flag = 0;
1419       [panel close];
1420       [unwind_data->pool release];
1421       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1422     }
1424   xfree (unwind_data);
1425   unblock_input ();
1429 Lisp_Object
1430 ns_popup_dialog (Lisp_Object position, Lisp_Object header, Lisp_Object contents)
1432   id dialog;
1433   Lisp_Object window, tem, title;
1434   struct frame *f;
1435   NSPoint p;
1436   BOOL isQ;
1437   NSAutoreleasePool *pool;
1439   NSTRACE (x-popup-dialog);
1441   isQ = NILP (header);
1443   if (EQ (position, Qt)
1444       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1445                                || EQ (XCAR (position), Qtool_bar))))
1446     {
1447       window = selected_window;
1448     }
1449   else if (CONSP (position))
1450     {
1451       Lisp_Object tem;
1452       tem = Fcar (position);
1453       if (XTYPE (tem) == Lisp_Cons)
1454         window = Fcar (Fcdr (position));
1455       else
1456         {
1457           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1458           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1459         }
1460     }
1461   else if (WINDOWP (position) || FRAMEP (position))
1462     {
1463       window = position;
1464     }
1465   else
1466     window = Qnil;
1468   if (FRAMEP (window))
1469     f = XFRAME (window);
1470   else if (WINDOWP (window))
1471     {
1472       CHECK_LIVE_WINDOW (window);
1473       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1474     }
1475   else
1476     CHECK_WINDOW (window);
1478   check_window_system (f);
1480   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1481   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1483   title = Fcar (contents);
1484   CHECK_STRING (title);
1486   if (NILP (Fcar (Fcdr (contents))))
1487     /* No buttons specified, add an "Ok" button so users can pop down
1488        the dialog.  */
1489     contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1491   block_input ();
1492   pool = [[NSAutoreleasePool alloc] init];
1493   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1494                                            isQuestion: isQ];
1496   {
1497     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1498     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1500     unwind_data->pool = pool;
1501     unwind_data->dialog = dialog;
1503     record_unwind_protect_ptr (pop_down_menu, unwind_data);
1504     popup_activated_flag = 1;
1505     tem = [dialog runDialogAt: p];
1506     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1507   }
1509   unblock_input ();
1511   return tem;
1515 /* ==========================================================================
1517     Popup Dialog: class implementation
1519    ========================================================================== */
1521 @interface FlippedView : NSView
1524 @end
1526 @implementation FlippedView
1527 - (BOOL)isFlipped
1529   return YES;
1531 @end
1533 @implementation EmacsDialogPanel
1535 #define SPACER          8.0
1536 #define ICONSIZE        64.0
1537 #define TEXTHEIGHT      20.0
1538 #define MINCELLWIDTH    90.0
1540 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1541               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1543   NSSize spacing = {SPACER, SPACER};
1544   NSRect area;
1545   id cell;
1546   NSImageView *imgView;
1547   FlippedView *contentView;
1548   NSImage *img;
1550   dialog_return   = Qundefined;
1551   button_values   = NULL;
1552   area.origin.x   = 3*SPACER;
1553   area.origin.y   = 2*SPACER;
1554   area.size.width = ICONSIZE;
1555   area.size.height= ICONSIZE;
1556   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1557   [img setScalesWhenResized: YES];
1558   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1559   imgView = [[NSImageView alloc] initWithFrame: area];
1560   [imgView setImage: img];
1561   [imgView setEditable: NO];
1562   [img autorelease];
1563   [imgView autorelease];
1565   aStyle = NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask;
1566   flag = YES;
1567   rows = 0;
1568   cols = 1;
1569   [super initWithContentRect: contentRect styleMask: aStyle
1570                      backing: backingType defer: flag];
1571   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1572   [contentView autorelease];
1574   [self setContentView: contentView];
1576   [[self contentView] setAutoresizesSubviews: YES];
1578   [[self contentView] addSubview: imgView];
1579   [self setTitle: @""];
1581   area.origin.x   += ICONSIZE+2*SPACER;
1582 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1583   area.size.width = 400;
1584   area.size.height= TEXTHEIGHT;
1585   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1586   [[self contentView] addSubview: command];
1587   [command setStringValue: ns_app_name];
1588   [command setDrawsBackground: NO];
1589   [command setBezeled: NO];
1590   [command setSelectable: NO];
1591   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1593 /*  area.origin.x   = ICONSIZE+2*SPACER;
1594   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1595   area.size.width = 400;
1596   area.size.height= 2;
1597   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1598   [[self contentView] addSubview: tem];
1599   [tem setTitlePosition: NSNoTitle];
1600   [tem setAutoresizingMask: NSViewWidthSizable];*/
1602 /*  area.origin.x = ICONSIZE+2*SPACER; */
1603   area.origin.y += TEXTHEIGHT+SPACER;
1604   area.size.width = 400;
1605   area.size.height= TEXTHEIGHT;
1606   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1607   [[self contentView] addSubview: title];
1608   [title setDrawsBackground: NO];
1609   [title setBezeled: NO];
1610   [title setSelectable: NO];
1611   [title setFont: [NSFont systemFontOfSize: 11.0]];
1613   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1614   [cell setBordered: NO];
1615   [cell setEnabled: NO];
1616   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1617   [cell setBezelStyle: NSRoundedBezelStyle];
1619   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1620                                       mode: NSHighlightModeMatrix
1621                                  prototype: cell
1622                               numberOfRows: 0
1623                            numberOfColumns: 1];
1624   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1625                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1626   [matrix setIntercellSpacing: spacing];
1627   [matrix autorelease];
1629   [[self contentView] addSubview: matrix];
1630   [self setOneShot: YES];
1631   [self setReleasedWhenClosed: YES];
1632   [self setHidesOnDeactivate: YES];
1633   return self;
1637 - (BOOL)windowShouldClose: (id)sender
1639   window_closed = YES;
1640   [NSApp stop:self];
1641   return NO;
1644 - (void)dealloc
1646   xfree (button_values);
1647   [super dealloc];
1650 - (void)process_dialog: (Lisp_Object) list
1652   Lisp_Object item, lst = list;
1653   int row = 0;
1654   int buttons = 0, btnnr = 0;
1656   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1657     {
1658       item = XCAR (list);
1659       if (XTYPE (item) == Lisp_Cons)
1660         ++buttons;
1661     }
1663   if (buttons > 0)
1664     button_values = xmalloc (buttons * sizeof *button_values);
1666   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1667     {
1668       item = XCAR (list);
1669       if (XTYPE (item) == Lisp_String)
1670         {
1671           [self addString: SSDATA (item) row: row++];
1672         }
1673       else if (XTYPE (item) == Lisp_Cons)
1674         {
1675           button_values[btnnr] = XCDR (item);
1676           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1677           ++btnnr;
1678         }
1679       else if (NILP (item))
1680         {
1681           [self addSplit];
1682           row = 0;
1683         }
1684     }
1688 - (void)addButton: (char *)str value: (int)tag row: (int)row
1690   id cell;
1692   if (row >= rows)
1693     {
1694       [matrix addRow];
1695       rows++;
1696     }
1697   cell = [matrix cellAtRow: row column: cols-1];
1698   [cell setTarget: self];
1699   [cell setAction: @selector (clicked: )];
1700   [cell setTitle: [NSString stringWithUTF8String: str]];
1701   [cell setTag: tag];
1702   [cell setBordered: YES];
1703   [cell setEnabled: YES];
1707 - (void)addString: (char *)str row: (int)row
1709   id cell;
1711   if (row >= rows)
1712     {
1713       [matrix addRow];
1714       rows++;
1715     }
1716   cell = [matrix cellAtRow: row column: cols-1];
1717   [cell setTitle: [NSString stringWithUTF8String: str]];
1718   [cell setBordered: YES];
1719   [cell setEnabled: NO];
1723 - (void)addSplit
1725   [matrix addColumn];
1726   cols++;
1730 - (void)clicked: sender
1732   NSArray *sellist = nil;
1733   EMACS_INT seltag;
1735   sellist = [sender selectedCells];
1736   if ([sellist count] < 1)
1737     return;
1739   seltag = [[sellist objectAtIndex: 0] tag];
1740   dialog_return = button_values[seltag];
1741   [NSApp stop:self];
1745 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1747   Lisp_Object head;
1748   [super init];
1750   if (XTYPE (contents) == Lisp_Cons)
1751     {
1752       head = Fcar (contents);
1753       [self process_dialog: Fcdr (contents)];
1754     }
1755   else
1756     head = contents;
1758   if (XTYPE (head) == Lisp_String)
1759       [title setStringValue:
1760                  [NSString stringWithUTF8String: SSDATA (head)]];
1761   else if (isQ == YES)
1762       [title setStringValue: @"Question"];
1763   else
1764       [title setStringValue: @"Information"];
1766   {
1767     int i;
1768     NSRect r, s, t;
1770     if (cols == 1 && rows > 1)  /* Never told where to split */
1771       {
1772         [matrix addColumn];
1773         for (i = 0; i < rows/2; i++)
1774           {
1775             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1776                       atRow: i column: 1];
1777             [matrix removeRow: (rows+1)/2];
1778           }
1779       }
1781     [matrix sizeToFit];
1782     {
1783       NSSize csize = [matrix cellSize];
1784       if (csize.width < MINCELLWIDTH)
1785         {
1786           csize.width = MINCELLWIDTH;
1787           [matrix setCellSize: csize];
1788           [matrix sizeToCells];
1789         }
1790     }
1792     [title sizeToFit];
1793     [command sizeToFit];
1795     t = [matrix frame];
1796     r = [title frame];
1797     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1798       {
1799         t.origin.x   = r.origin.x;
1800         t.size.width = r.size.width;
1801       }
1802     r = [command 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       }
1809     r = [self frame];
1810     s = [(NSView *)[self contentView] frame];
1811     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1812     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1813     [self setFrame: r display: NO];
1814   }
1816   return self;
1821 - (void)timeout_handler: (NSTimer *)timedEntry
1823   NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1824                             location: NSMakePoint (0, 0)
1825                        modifierFlags: 0
1826                            timestamp: 0
1827                         windowNumber: [[NSApp mainWindow] windowNumber]
1828                              context: [NSApp context]
1829                              subtype: 0
1830                                data1: 0
1831                                data2: 0];
1833   timer_fired = YES;
1834   /* We use sto because stopModal/abortModal out of the main loop does not
1835      seem to work in 10.6.  But as we use stop we must send a real event so
1836      the stop is seen and acted upon.  */
1837   [NSApp stop:self];
1838   [NSApp postEvent: nxev atStart: NO];
1841 - (Lisp_Object)runDialogAt: (NSPoint)p
1843   Lisp_Object ret = Qundefined;
1845   while (popup_activated_flag)
1846     {
1847       NSTimer *tmo = nil;
1848       struct timespec next_time = timer_check ();
1850       if (timespec_valid_p (next_time))
1851         {
1852           double time = timespectod (next_time);
1853           tmo = [NSTimer timerWithTimeInterval: time
1854                                         target: self
1855                                       selector: @selector (timeout_handler:)
1856                                       userInfo: 0
1857                                        repeats: NO];
1858           [[NSRunLoop currentRunLoop] addTimer: tmo
1859                                        forMode: NSModalPanelRunLoopMode];
1860         }
1861       timer_fired = NO;
1862       dialog_return = Qundefined;
1863       [NSApp runModalForWindow: self];
1864       ret = dialog_return;
1865       if (! timer_fired)
1866         {
1867           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1868           break;
1869         }
1870     }
1872   if (EQ (ret, Qundefined) && window_closed)
1873     /* Make close button pressed equivalent to C-g.  */
1874     Fsignal (Qquit, Qnil);
1876   return ret;
1879 @end
1882 /* ==========================================================================
1884     Lisp definitions
1886    ========================================================================== */
1888 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1889        doc: /* Cause the NS menu to be re-calculated.  */)
1890      (void)
1892   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1893   return Qnil;
1897 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1898        doc: /* Return t if a menu or popup dialog is active.  */)
1899      (void)
1901   return popup_activated () ? Qt : Qnil;
1904 /* ==========================================================================
1906     Lisp interface declaration
1908    ========================================================================== */
1910 void
1911 syms_of_nsmenu (void)
1913 #ifndef NS_IMPL_COCOA
1914   /* Don't know how to keep track of this in Next/Open/GNUstep.  Always
1915      update menus there.  */
1916   trackingMenu = 1;
1917 #endif
1918   defsubr (&Sns_reset_menu);
1919   defsubr (&Smenu_or_popup_active_p);
1921   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1922   staticpro (&Qdebug_on_next_call);