* src/eval.c (Fcalled_interactively_p): Doc fix. (Bug#11747)
[emacs.git] / src / nsmenu.m
blob1fb795243ae7e2d4b079af6235678b8a5bf5c168
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007-2012 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>
27 #include <setjmp.h>
29 #include "lisp.h"
30 #include "window.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 #define MenuStagger 10.0
50 #if 0
51 int menu_trace_num = 0;
52 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
53                                 __FILE__, __LINE__, ++menu_trace_num)
54 #else
55 #define NSTRACE(x)
56 #endif
58 #if 0
59 /* Include lisp -> C common menu parsing code */
60 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
61 #include "nsmenu_common.c"
62 #endif
64 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
65 extern Lisp_Object QCtoggle, QCradio;
67 Lisp_Object Qdebug_on_next_call;
68 extern Lisp_Object Qoverriding_local_map, Qoverriding_terminal_local_map;
70 extern long context_menu_value;
71 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
73 /* Nonzero means a menu is currently active.  */
74 static int popup_activated_flag;
75 static NSModalSession popupSession;
77 /* Nonzero means we are tracking and updating menus.  */
78 static int trackingMenu;
81 /* NOTE: toolbar implementation is at end,
82   following complete menu implementation. */
85 /* ==========================================================================
87     Menu: Externally-called functions
89    ========================================================================== */
92 /* FIXME: not currently used, but should normalize with other terms. */
93 void
94 x_activate_menubar (struct frame *f)
96     fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
100 /* Supposed to discard menubar and free storage.  Since we share the
101    menubar among frames and update its context for the focused window,
102    there is nothing to do here. */
103 void
104 free_frame_menubar (struct frame *f)
106   return;
111 popup_activated (void)
113   return popup_activated_flag;
117 /* --------------------------------------------------------------------------
118     Update menubar.  Three cases:
119     1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
120        just top-level menu strings (OS X), or goto case (2) (GNUstep).
121     2) deep_p = 1, submenu = nil: Recompute all submenus.
122     3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
123    -------------------------------------------------------------------------- */
124 void
125 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
127   NSAutoreleasePool *pool;
128   id menu = [NSApp mainMenu];
129   static EmacsMenu *last_submenu = nil;
130   BOOL needsSet = NO;
131   const char *submenuTitle = [[submenu title] UTF8String];
132   extern int waiting_for_input;
133   int owfi;
134   Lisp_Object items;
135   widget_value *wv, *first_wv, *prev_wv = 0;
136   int i;
138 #if NSMENUPROFILE
139   struct timeb tb;
140   long t;
141 #endif
143   NSTRACE (set_frame_menubar);
145   if (f != SELECTED_FRAME ())
146       return;
147   XSETFRAME (Vmenu_updating_frame, f);
148 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
150   BLOCK_INPUT;
151   pool = [[NSAutoreleasePool alloc] init];
153   /* Menu may have been created automatically; if so, discard it. */
154   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
155     {
156       [menu release];
157       menu = nil;
158     }
160   if (menu == nil)
161     {
162       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
163       needsSet = YES;
164     }
165   else
166     {  /* close up anything on there */
167       id attMenu = [menu attachedMenu];
168       if (attMenu != nil)
169         [attMenu close];
170     }
172 #if NSMENUPROFILE
173   ftime (&tb);
174   t = -(1000*tb.time+tb.millitm);
175 #endif
177 #ifdef NS_IMPL_GNUSTEP
178   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
179 #endif
181   if (deep_p)
182     {
183       /* Fully parse one or more of the submenus. */
184       int n = 0;
185       int *submenu_start, *submenu_end;
186       int *submenu_top_level_items, *submenu_n_panes;
187       struct buffer *prev = current_buffer;
188       Lisp_Object buffer;
189       int specpdl_count = SPECPDL_INDEX ();
190       int previous_menu_items_used = f->menu_bar_items_used;
191       Lisp_Object *previous_items
192         = (Lisp_Object *) alloca (previous_menu_items_used
193                                   * sizeof (Lisp_Object));
195       /* lisp preliminaries */
196       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
197       specbind (Qinhibit_quit, Qt);
198       specbind (Qdebug_on_next_call, Qnil);
199       record_unwind_save_match_data ();
200       if (NILP (Voverriding_local_map_menu_flag))
201         {
202           specbind (Qoverriding_terminal_local_map, Qnil);
203           specbind (Qoverriding_local_map, Qnil);
204         }
205       set_buffer_internal_1 (XBUFFER (buffer));
207       /* TODO: for some reason this is not needed in other terms,
208            but some menu updates call Info-extract-pointer which causes
209            abort-on-error if waiting-for-input.  Needs further investigation. */
210       owfi = waiting_for_input;
211       waiting_for_input = 0;
213       /* lucid hook and possible reset */
214       safe_run_hooks (Qactivate_menubar_hook);
215       if (! NILP (Vlucid_menu_bar_dirty_flag))
216         call0 (Qrecompute_lucid_menubar);
217       safe_run_hooks (Qmenu_bar_update_hook);
218       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
220       /* Now ready to go */
221       items = FRAME_MENU_BAR_ITEMS (f);
223       /* Save the frame's previous menu bar contents data */
224       if (previous_menu_items_used)
225         memcpy (previous_items, &AREF (f->menu_bar_vector, 0),
226                 previous_menu_items_used * sizeof (Lisp_Object));
228       /* parse stage 1: extract from lisp */
229       save_menu_items ();
231       menu_items = f->menu_bar_vector;
232       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
233       submenu_start = (int *) alloca (ASIZE (items) * sizeof (int *));
234       submenu_end = (int *) alloca (ASIZE (items) * sizeof (int *));
235       submenu_n_panes = (int *) alloca (ASIZE (items) * sizeof (int));
236       submenu_top_level_items
237         = (int *) alloca (ASIZE (items) * sizeof (int *));
238       init_menu_items ();
239       for (i = 0; i < ASIZE (items); i += 4)
240         {
241           Lisp_Object key, string, maps;
243           key = AREF (items, i);
244           string = AREF (items, i + 1);
245           maps = AREF (items, i + 2);
246           if (NILP (string))
247             break;
249           /* FIXME: we'd like to only parse the needed submenu, but this
250                was causing crashes in the _common parsing code.. need to make
251                sure proper initialization done.. */
252 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
253              continue; */
255           submenu_start[i] = menu_items_used;
257           menu_items_n_panes = 0;
258           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
259           submenu_n_panes[i] = menu_items_n_panes;
260           submenu_end[i] = menu_items_used;
261           n++;
262         }
264       finish_menu_items ();
265       waiting_for_input = owfi;
268       if (submenu && n == 0)
269         {
270           /* should have found a menu for this one but didn't */
271           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
272                   submenuTitle);
273           discard_menu_items ();
274           unbind_to (specpdl_count, Qnil);
275           [pool release];
276           UNBLOCK_INPUT;
277           return;
278         }
280       /* parse stage 2: insert into lucid 'widget_value' structures
281          [comments in other terms say not to evaluate lisp code here] */
282       wv = xmalloc_widget_value ();
283       wv->name = "menubar";
284       wv->value = 0;
285       wv->enabled = 1;
286       wv->button_type = BUTTON_TYPE_NONE;
287       wv->help = Qnil;
288       first_wv = wv;
290       for (i = 0; i < 4*n; i += 4)
291         {
292           menu_items_n_panes = submenu_n_panes[i];
293           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
294                                       submenu_top_level_items[i]);
295           if (prev_wv)
296             prev_wv->next = wv;
297           else
298             first_wv->contents = wv;
299           /* Don't set wv->name here; GC during the loop might relocate it.  */
300           wv->enabled = 1;
301           wv->button_type = BUTTON_TYPE_NONE;
302           prev_wv = wv;
303         }
305       set_buffer_internal_1 (prev);
307       /* Compare the new menu items with previous, and leave off if no change */
308       /* FIXME: following other terms here, but seems like this should be
309            done before parse stage 2 above, since its results aren't used */
310       if (previous_menu_items_used
311           && (!submenu || (submenu && submenu == last_submenu))
312           && menu_items_used == previous_menu_items_used)
313         {
314           for (i = 0; i < previous_menu_items_used; i++)
315             /* FIXME: this ALWAYS fails on Buffers menu items.. something
316                  about their strings causes them to change every time, so we
317                  double-check failures */
318             if (!EQ (previous_items[i], AREF (menu_items, i)))
319               if (!(STRINGP (previous_items[i])
320                     && STRINGP (AREF (menu_items, i))
321                     && !strcmp (SDATA (previous_items[i]),
322                                 SDATA (AREF (menu_items, i)))))
323                   break;
324           if (i == previous_menu_items_used)
325             {
326               /* No change.. */
328 #if NSMENUPROFILE
329               ftime (&tb);
330               t += 1000*tb.time+tb.millitm;
331               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
332 #endif
334               free_menubar_widget_value_tree (first_wv);
335               discard_menu_items ();
336               unbind_to (specpdl_count, Qnil);
337               [pool release];
338               UNBLOCK_INPUT;
339               return;
340             }
341         }
342       /* The menu items are different, so store them in the frame */
343       /* FIXME: this is not correct for single-submenu case */
344       f->menu_bar_vector = menu_items;
345       f->menu_bar_items_used = menu_items_used;
347       /* Calls restore_menu_items, etc., as they were outside */
348       unbind_to (specpdl_count, Qnil);
350       /* Parse stage 2a: now GC cannot happen during the lifetime of the
351          widget_value, so it's safe to store data from a Lisp_String */
352       wv = first_wv->contents;
353       for (i = 0; i < ASIZE (items); i += 4)
354         {
355           Lisp_Object string;
356           string = AREF (items, i + 1);
357           if (NILP (string))
358             break;
359 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
360                continue; */
362           wv->name = SSDATA (string);
363           update_submenu_strings (wv->contents);
364           wv = wv->next;
365         }
367       /* Now, update the NS menu; if we have a submenu, use that, otherwise
368          create a new menu for each sub and fill it. */
369       if (submenu)
370         {
371           for (wv = first_wv->contents; wv; wv = wv->next)
372             {
373               if (!strcmp (submenuTitle, wv->name))
374                 {
375                   [submenu fillWithWidgetValue: wv->contents];
376                   last_submenu = submenu;
377                   break;
378                 }
379             }
380         }
381       else
382         {
383           [menu fillWithWidgetValue: first_wv->contents];
384         }
386     }
387   else
388     {
389       static int n_previous_strings = 0;
390       static char previous_strings[100][10];
391       static struct frame *last_f = NULL;
392       int n;
393       Lisp_Object string;
395       wv = xmalloc_widget_value ();
396       wv->name = "menubar";
397       wv->value = 0;
398       wv->enabled = 1;
399       wv->button_type = BUTTON_TYPE_NONE;
400       wv->help = Qnil;
401       first_wv = wv;
403       /* Make widget-value tree w/ just the top level menu bar strings */
404       items = FRAME_MENU_BAR_ITEMS (f);
405       if (NILP (items))
406         {
407           free_menubar_widget_value_tree (first_wv);
408           [pool release];
409           UNBLOCK_INPUT;
410           return;
411         }
414       /* check if no change.. this mechanism is a bit rough, but ready */
415       n = ASIZE (items) / 4;
416       if (f == last_f && n_previous_strings == n)
417         {
418           for (i = 0; i<n; i++)
419             {
420               string = AREF (items, 4*i+1);
422               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
423                 continue;
424               if (NILP (string))
425                 {
426                   if (previous_strings[i][0])
427                     break;
428                   else
429                     continue;
430                 }
431               else if (strncmp (previous_strings[i], SDATA (string), 10))
432                 break;
433             }
435           if (i == n)
436             {
437               free_menubar_widget_value_tree (first_wv);
438               [pool release];
439               UNBLOCK_INPUT;
440               return;
441             }
442         }
444       [menu clear];
445       for (i = 0; i < ASIZE (items); i += 4)
446         {
447           string = AREF (items, i + 1);
448           if (NILP (string))
449             break;
451           if (n < 100)
452             strncpy (previous_strings[i/4], SDATA (string), 10);
454           wv = xmalloc_widget_value ();
455           wv->name = SSDATA (string);
456           wv->value = 0;
457           wv->enabled = 1;
458           wv->button_type = BUTTON_TYPE_NONE;
459           wv->help = Qnil;
460           wv->call_data = (void *) (EMACS_INT) (-1);
462 #ifdef NS_IMPL_COCOA
463           /* we'll update the real copy under app menu when time comes */
464           if (!strcmp ("Services", wv->name))
465             {
466               /* but we need to make sure it will update on demand */
467               [svcsMenu setFrame: f];
468             }
469           else
470 #endif
471           [menu addSubmenuWithTitle: wv->name forFrame: f];
473           if (prev_wv)
474             prev_wv->next = wv;
475           else
476             first_wv->contents = wv;
477           prev_wv = wv;
478         }
480       last_f = f;
481       if (n < 100)
482         n_previous_strings = n;
483       else
484         n_previous_strings = 0;
486     }
487   free_menubar_widget_value_tree (first_wv);
490 #if NSMENUPROFILE
491   ftime (&tb);
492   t += 1000*tb.time+tb.millitm;
493   fprintf (stderr, "Menu update took %ld msec.\n", t);
494 #endif
496   /* set main menu */
497   if (needsSet)
498     [NSApp setMainMenu: menu];
500   [pool release];
501   UNBLOCK_INPUT;
506 /* Main emacs core entry point for menubar menus: called to indicate that the
507    frame's menus have changed, and the *step representation should be updated
508    from Lisp. */
509 void
510 set_frame_menubar (struct frame *f, int first_time, int deep_p)
512   ns_update_menubar (f, deep_p, nil);
516 /* ==========================================================================
518     Menu: class implementation
520    ========================================================================== */
523 /* Menu that can define itself from Emacs "widget_value"s and will lazily
524    update itself when user clicked.  Based on Carbon/AppKit implementation
525    by Yamamoto Mitsuharu. */
526 @implementation EmacsMenu
528 /* override designated initializer */
529 - initWithTitle: (NSString *)title
531   if (self = [super initWithTitle: title])
532     [self setAutoenablesItems: NO];
533   return self;
537 /* used for top-level */
538 - initWithTitle: (NSString *)title frame: (struct frame *)f
540   [self initWithTitle: title];
541   frame = f;
542 #ifdef NS_IMPL_COCOA
543   [self setDelegate: self];
544 #endif
545   return self;
549 - (void)setFrame: (struct frame *)f
551   frame = f;
554 #ifdef NS_IMPL_COCOA
555 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
556 extern NSString *NSMenuDidBeginTrackingNotification;
557 #endif
558 #endif
560 #ifdef NS_IMPL_COCOA
561 -(void)trackingNotification:(NSNotification *)notification
563   /* Update menu in menuNeedsUpdate only while tracking menus.  */
564   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
565                   ? 1 : 0);
567 #endif
569 /* delegate method called when a submenu is being opened: run a 'deep' call
570    to set_frame_menubar */
571 - (void)menuNeedsUpdate: (NSMenu *)menu
573   if (!FRAME_LIVE_P (frame))
574     return;
576   /* Cocoa/Carbon will request update on every keystroke
577      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
578      since key equivalents are handled through emacs.
579      On Leopard, even keystroke events generate SystemDefined event.
580      Third-party applications that enhance mouse / trackpad
581      interaction, or also VNC/Remote Desktop will send events
582      of type AppDefined rather than SysDefined.
583      Menus will fail to show up if they haven't been initialized.
584      AppDefined events may lack timing data.
586      Thus, we rely on the didBeginTrackingNotification notification
587      as above to indicate the need for updates.
588      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
589      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
590   */
591   if (trackingMenu == 0
592       /* Also, don't try this if from an event picked up asynchronously,
593          as lots of lisp evaluation happens in ns_update_menubar. */
594       || handling_signal != 0)
595     return;
596 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
597   ns_update_menubar (frame, 1, self);
601 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
603   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
604       && FRAME_NS_VIEW (SELECTED_FRAME ()))
605     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
606   return YES;
610 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
611    into an accelerator string.  We are only able to display a single character
612    for an accelerator, together with an optional modifier combination.  (Under
613    Carbon more control was possible, but in Cocoa multi-char strings passed to
614    NSMenuItem get ignored.  For now we try to display a super-single letter
615    combo, and return the others as strings to be appended to the item title.
616    (This is signaled by setting keyEquivModMask to 0 for now.) */
617 -(NSString *)parseKeyEquiv: (const char *)key
619   const char *tpos = key;
620   keyEquivModMask = NSCommandKeyMask;
622   if (!key || !strlen (key))
623     return @"";
625   while (*tpos == ' ' || *tpos == '(')
626     tpos++;
627   if ((*tpos == 's') && (*(tpos+1) == '-'))
628     {
629       return [NSString stringWithFormat: @"%c", tpos[2]];
630     }
631   keyEquivModMask = 0; /* signal */
632   return [NSString stringWithUTF8String: tpos];
636 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
638   NSMenuItem *item;
639   widget_value *wv = (widget_value *)wvptr;
641   if (menu_separator_name_p (wv->name))
642     {
643       item = [NSMenuItem separatorItem];
644       [self addItem: item];
645     }
646   else
647     {
648       NSString *title, *keyEq;
649       title = [NSString stringWithUTF8String: wv->name];
650       if (title == nil)
651         title = @"< ? >";  /* (get out in the open so we know about it) */
653       keyEq = [self parseKeyEquiv: wv->key];
654 #ifdef NS_IMPL_COCOA
655       /* OS X just ignores modifier strings longer than one character */
656       if (keyEquivModMask == 0)
657         title = [title stringByAppendingFormat: @" (%@)", keyEq];
658 #endif
660       item = [self addItemWithTitle: (NSString *)title
661                              action: @selector (menuDown:)
662                       keyEquivalent: keyEq];
663       [item setKeyEquivalentModifierMask: keyEquivModMask];
665       [item setEnabled: wv->enabled];
667       /* Draw radio buttons and tickboxes */
668       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
669                            wv->button_type == BUTTON_TYPE_RADIO))
670         [item setState: NSOnState];
671       else
672         [item setState: NSOffState];
674       [item setTag: (NSInteger)wv->call_data];
675     }
677   return item;
681 /* convenience */
682 -(void)clear
684   int n;
686   for (n = [self numberOfItems]-1; n >= 0; n--)
687     {
688       NSMenuItem *item = [self itemAtIndex: n];
689       NSString *title = [item title];
690       if (([title length] == 0  /* OSX 10.5 */
691            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
692            || [@"Apple" isEqualToString: title]) /* older */
693           && ![item isSeparatorItem])
694         continue;
695       [self removeItemAtIndex: n];
696     }
700 - (void)fillWithWidgetValue: (void *)wvptr
702   widget_value *wv = (widget_value *)wvptr;
704   /* clear existing contents */
705   [self setMenuChangedMessagesEnabled: NO];
706   [self clear];
708   /* add new contents */
709   for (; wv != NULL; wv = wv->next)
710     {
711       NSMenuItem *item = [self addItemWithWidgetValue: wv];
713       if (wv->contents)
714         {
715           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
717           [self setSubmenu: submenu forItem: item];
718           [submenu fillWithWidgetValue: wv->contents];
719           [submenu release];
720           [item setAction: nil];
721         }
722     }
724   [self setMenuChangedMessagesEnabled: YES];
725 #ifdef NS_IMPL_GNUSTEP
726   if ([[self window] isVisible])
727     [self sizeToFit];
728 #else
729 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_2
730   if ([self supermenu] == nil)
731     [self sizeToFit];
732 #endif
733 #endif
737 /* adds an empty submenu and returns it */
738 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
740   NSString *titleStr = [NSString stringWithUTF8String: title];
741   NSMenuItem *item = [self addItemWithTitle: titleStr
742                                      action: nil /*@selector (menuDown:) */
743                               keyEquivalent: @""];
744   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
745   [self setSubmenu: submenu forItem: item];
746   [submenu release];
747   return submenu;
750 /* run a menu in popup mode */
751 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
752                  keymaps: (int)keymaps
754   EmacsView *view = FRAME_NS_VIEW (f);
755   NSEvent *e, *event;
756   long retVal;
758 /*   p = [view convertPoint:p fromView: nil]; */
759   p.y = NSHeight ([view frame]) - p.y;
760   e = [[view window] currentEvent];
761    event = [NSEvent mouseEventWithType: NSRightMouseDown
762                               location: p
763                          modifierFlags: 0
764                              timestamp: [e timestamp]
765                           windowNumber: [[view window] windowNumber]
766                                context: [e context]
767                            eventNumber: 0/*[e eventNumber] */
768                             clickCount: 1
769                               pressure: 0];
771   context_menu_value = -1;
772   [NSMenu popUpContextMenu: self withEvent: event forView: view];
773   retVal = context_menu_value;
774   context_menu_value = 0;
775   return retVal > 0
776       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
777       : Qnil;
780 @end  /* EmacsMenu */
784 /* ==========================================================================
786     Context Menu: implementing functions
788    ========================================================================== */
790 Lisp_Object
791 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
792               Lisp_Object title, const char **error)
794   EmacsMenu *pmenu;
795   NSPoint p;
796   Lisp_Object window, tem, keymap;
797   int specpdl_count = SPECPDL_INDEX ();
798   widget_value *wv, *first_wv = 0;
800   p.x = x; p.y = y;
802   /* now parse stage 2 as in ns_update_menubar */
803   wv = xmalloc_widget_value ();
804   wv->name = "contextmenu";
805   wv->value = 0;
806   wv->enabled = 1;
807   wv->button_type = BUTTON_TYPE_NONE;
808   wv->help = Qnil;
809   first_wv = wv;
811 #if 0
812   /* FIXME: a couple of one-line differences prevent reuse */
813   wv = digest_single_submenu (0, menu_items_used, Qnil);
814 #else
815   {
816   widget_value *save_wv = 0, *prev_wv = 0;
817   widget_value **submenu_stack
818     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
819 /*   Lisp_Object *subprefix_stack
820        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
821   int submenu_depth = 0;
822   int first_pane = 1;
823   int i;
825   /* Loop over all panes and items, filling in the tree.  */
826   i = 0;
827   while (i < menu_items_used)
828     {
829       if (EQ (AREF (menu_items, i), Qnil))
830         {
831           submenu_stack[submenu_depth++] = save_wv;
832           save_wv = prev_wv;
833           prev_wv = 0;
834           first_pane = 1;
835           i++;
836         }
837       else if (EQ (AREF (menu_items, i), Qlambda))
838         {
839           prev_wv = save_wv;
840           save_wv = submenu_stack[--submenu_depth];
841           first_pane = 0;
842           i++;
843         }
844       else if (EQ (AREF (menu_items, i), Qt)
845                && submenu_depth != 0)
846         i += MENU_ITEMS_PANE_LENGTH;
847       /* Ignore a nil in the item list.
848          It's meaningful only for dialog boxes.  */
849       else if (EQ (AREF (menu_items, i), Qquote))
850         i += 1;
851       else if (EQ (AREF (menu_items, i), Qt))
852         {
853           /* Create a new pane.  */
854           Lisp_Object pane_name, prefix;
855           const char *pane_string;
857           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
858           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
860 #ifndef HAVE_MULTILINGUAL_MENU
861           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
862             {
863               pane_name = ENCODE_MENU_STRING (pane_name);
864               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
865             }
866 #endif
867           pane_string = (NILP (pane_name)
868                          ? "" : SSDATA (pane_name));
869           /* If there is just one top-level pane, put all its items directly
870              under the top-level menu.  */
871           if (menu_items_n_panes == 1)
872             pane_string = "";
874           /* If the pane has a meaningful name,
875              make the pane a top-level menu item
876              with its items as a submenu beneath it.  */
877           if (!keymaps && strcmp (pane_string, ""))
878             {
879               wv = xmalloc_widget_value ();
880               if (save_wv)
881                 save_wv->next = wv;
882               else
883                 first_wv->contents = wv;
884               wv->name = pane_string;
885               if (keymaps && !NILP (prefix))
886                 wv->name++;
887               wv->value = 0;
888               wv->enabled = 1;
889               wv->button_type = BUTTON_TYPE_NONE;
890               wv->help = Qnil;
891               save_wv = wv;
892               prev_wv = 0;
893             }
894           else if (first_pane)
895             {
896               save_wv = wv;
897               prev_wv = 0;
898             }
899           first_pane = 0;
900           i += MENU_ITEMS_PANE_LENGTH;
901         }
902       else
903         {
904           /* Create a new item within current pane.  */
905           Lisp_Object item_name, enable, descrip, def, type, selected, help;
906           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
907           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
908           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
909           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
910           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
911           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
912           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
914 #ifndef HAVE_MULTILINGUAL_MENU
915           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
916             {
917               item_name = ENCODE_MENU_STRING (item_name);
918               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
919             }
921           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
922             {
923               descrip = ENCODE_MENU_STRING (descrip);
924               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
925             }
926 #endif /* not HAVE_MULTILINGUAL_MENU */
928           wv = xmalloc_widget_value ();
929           if (prev_wv)
930             prev_wv->next = wv;
931           else
932             save_wv->contents = wv;
933           wv->name = SSDATA (item_name);
934           if (!NILP (descrip))
935             wv->key = SSDATA (descrip);
936           wv->value = 0;
937           /* If this item has a null value,
938              make the call_data null so that it won't display a box
939              when the mouse is on it.  */
940           wv->call_data
941               = !NILP (def) ? (void *) &AREF (menu_items, i) : 0;
942           wv->enabled = !NILP (enable);
944           if (NILP (type))
945             wv->button_type = BUTTON_TYPE_NONE;
946           else if (EQ (type, QCtoggle))
947             wv->button_type = BUTTON_TYPE_TOGGLE;
948           else if (EQ (type, QCradio))
949             wv->button_type = BUTTON_TYPE_RADIO;
950           else
951             abort ();
953           wv->selected = !NILP (selected);
955           if (! STRINGP (help))
956             help = Qnil;
958           wv->help = help;
960           prev_wv = wv;
962           i += MENU_ITEMS_ITEM_LENGTH;
963         }
964     }
965   }
966 #endif
968   if (!NILP (title))
969     {
970       widget_value *wv_title = xmalloc_widget_value ();
971       widget_value *wv_sep = xmalloc_widget_value ();
973       /* Maybe replace this separator with a bitmap or owner-draw item
974          so that it looks better.  Having two separators looks odd.  */
975       wv_sep->name = "--";
976       wv_sep->next = first_wv->contents;
977       wv_sep->help = Qnil;
979 #ifndef HAVE_MULTILINGUAL_MENU
980       if (STRING_MULTIBYTE (title))
981         title = ENCODE_MENU_STRING (title);
982 #endif
984       wv_title->name = SSDATA (title);
985       wv_title->enabled = NO;
986       wv_title->button_type = BUTTON_TYPE_NONE;
987       wv_title->help = Qnil;
988       wv_title->next = wv_sep;
989       first_wv->contents = wv_title;
990     }
992   pmenu = [[EmacsMenu alloc] initWithTitle:
993                                [NSString stringWithUTF8String: SDATA (title)]];
994   [pmenu fillWithWidgetValue: first_wv->contents];
995   free_menubar_widget_value_tree (first_wv);
996   unbind_to (specpdl_count, Qnil);
998   popup_activated_flag = 1;
999   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1000   popup_activated_flag = 0;
1001   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1003   return tem;
1007 /* ==========================================================================
1009     Toolbar: externally-called functions
1011    ========================================================================== */
1013 void
1014 free_frame_tool_bar (FRAME_PTR f)
1015 /* --------------------------------------------------------------------------
1016     Under NS we just hide the toolbar until it might be needed again.
1017    -------------------------------------------------------------------------- */
1019   BLOCK_INPUT;
1020   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1021   FRAME_TOOLBAR_HEIGHT (f) = 0;
1022   UNBLOCK_INPUT;
1025 void
1026 update_frame_tool_bar (FRAME_PTR f)
1027 /* --------------------------------------------------------------------------
1028     Update toolbar contents
1029    -------------------------------------------------------------------------- */
1031   int i;
1032   EmacsView *view = FRAME_NS_VIEW (f);
1033   NSWindow *window = [view window];
1034   EmacsToolbar *toolbar = [view toolbar];
1036   BLOCK_INPUT;
1037   [toolbar clearActive];
1039   /* update EmacsToolbar as in GtkUtils, build items list */
1040   for (i = 0; i < f->n_tool_bar_items; ++i)
1041     {
1042 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1043                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1045       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1046       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1047       int idx;
1048       ptrdiff_t img_id;
1049       struct image *img;
1050       Lisp_Object image;
1051       Lisp_Object helpObj;
1052       const char *helpText;
1054       /* If image is a vector, choose the image according to the
1055          button state.  */
1056       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1057       if (VECTORP (image))
1058         {
1059           /* NS toolbar auto-computes disabled and selected images */
1060           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1061           xassert (ASIZE (image) >= idx);
1062           image = AREF (image, idx);
1063         }
1064       else
1065         {
1066           idx = -1;
1067         }
1068       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1069       if (NILP (helpObj))
1070         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1071       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1073       /* Ignore invalid image specifications.  */
1074       if (!valid_image_p (image))
1075         {
1076           /* Don't log anything, GNUS makes invalid images all the time.  */
1077           continue;
1078         }
1080       img_id = lookup_image (f, image);
1081       img = IMAGE_FROM_ID (f, img_id);
1082       prepare_image_for_display (f, img);
1084       if (img->load_failed_p || img->pixmap == nil)
1085         {
1086           NSLog (@"Could not prepare toolbar image for display.");
1087           continue;
1088         }
1090       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1091                                enabled: enabled_p];
1092 #undef TOOLPROP
1093     }
1095   if (![toolbar isVisible])
1096       [toolbar setVisible: YES];
1098   if ([toolbar changed])
1099     {
1100       /* inform app that toolbar has changed */
1101       NSDictionary *dict = [toolbar configurationDictionary];
1102       NSMutableDictionary *newDict = [dict mutableCopy];
1103       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1104       NSObject *key;
1105       while ((key = [keys nextObject]) != nil)
1106         {
1107           NSObject *val = [dict objectForKey: key];
1108           if ([val isKindOfClass: [NSArray class]])
1109             {
1110               [newDict setObject:
1111                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1112                           forKey: key];
1113               break;
1114             }
1115         }
1116       [toolbar setConfigurationFromDictionary: newDict];
1117       [newDict release];
1118     }
1120   FRAME_TOOLBAR_HEIGHT (f) =
1121     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1122     - FRAME_NS_TITLEBAR_HEIGHT (f);
1123   UNBLOCK_INPUT;
1127 /* ==========================================================================
1129     Toolbar: class implementation
1131    ========================================================================== */
1133 @implementation EmacsToolbar
1135 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1137   self = [super initWithIdentifier: identifier];
1138   emacsView = view;
1139   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1140   [self setSizeMode: NSToolbarSizeModeSmall];
1141   [self setDelegate: self];
1142   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1143   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1144   prevEnablement = enablement = 0L;
1145   return self;
1148 - (void)dealloc
1150   [prevIdentifiers release];
1151   [activeIdentifiers release];
1152   [identifierToItem release];
1153   [super dealloc];
1156 - (void) clearActive
1158   [prevIdentifiers release];
1159   prevIdentifiers = [activeIdentifiers copy];
1160   [activeIdentifiers removeAllObjects];
1161   prevEnablement = enablement;
1162   enablement = 0L;
1165 - (BOOL) changed
1167   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1168     enablement == prevEnablement ? NO : YES;
1171 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1172                         helpText: (const char *)help enabled: (BOOL)enabled
1174   /* 1) come up w/identifier */
1175   NSString *identifier
1176       = [NSString stringWithFormat: @"%u", [img hash]];
1178   /* 2) create / reuse item */
1179   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1180   if (item == nil)
1181     {
1182       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1183                autorelease];
1184       [item setImage: img];
1185       [item setToolTip: [NSString stringWithUTF8String: help]];
1186       [item setTarget: emacsView];
1187       [item setAction: @selector (toolbarClicked:)];
1188     }
1190   [item setTag: idx];
1191   [item setEnabled: enabled];
1193   /* 3) update state */
1194   [identifierToItem setObject: item forKey: identifier];
1195   [activeIdentifiers addObject: identifier];
1196   enablement = (enablement << 1) | (enabled == YES);
1199 /* This overrides super's implementation, which automatically sets
1200    all items to enabled state (for some reason). */
1201 - (void)validateVisibleItems { }
1204 /* delegate methods */
1206 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1207       itemForItemIdentifier: (NSString *)itemIdentifier
1208   willBeInsertedIntoToolbar: (BOOL)flag
1210   /* look up NSToolbarItem by identifier and return... */
1211   return [identifierToItem objectForKey: itemIdentifier];
1214 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1216   /* return entire set.. */
1217   return activeIdentifiers;
1220 /* for configuration palette (not yet supported) */
1221 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1223   /* return entire set... */
1224   return [identifierToItem allKeys];
1227 /* optional and unneeded */
1228 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1229 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1230 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1232 @end  /* EmacsToolbar */
1236 /* ==========================================================================
1238     Tooltip: class implementation
1240    ========================================================================== */
1242 /* Needed because NeXTstep does not provide enough control over tooltip
1243    display. */
1244 @implementation EmacsTooltip
1246 - init
1248   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1249                                             blue: 0.792 alpha: 0.95];
1250   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1251   NSFont *sfont = [font screenFont];
1252   int height = [sfont ascender] - [sfont descender];
1253 /*[font boundingRectForFont].size.height; */
1254   NSRect r = NSMakeRect (0, 0, 100, height+6);
1256   textField = [[NSTextField alloc] initWithFrame: r];
1257   [textField setFont: font];
1258   [textField setBackgroundColor: col];
1260   [textField setEditable: NO];
1261   [textField setSelectable: NO];
1262   [textField setBordered: NO];
1263   [textField setBezeled: NO];
1264   [textField setDrawsBackground: YES];
1266   win = [[NSWindow alloc]
1267             initWithContentRect: [textField frame]
1268                       styleMask: 0
1269                         backing: NSBackingStoreBuffered
1270                           defer: YES];
1271   [win setHasShadow: YES];
1272   [win setReleasedWhenClosed: NO];
1273   [win setDelegate: self];
1274   [[win contentView] addSubview: textField];
1275 /*  [win setBackgroundColor: col]; */
1276   [win setOpaque: NO];
1278   return self;
1281 - (void) dealloc
1283   [win close];
1284   [win release];
1285   [textField release];
1286   [super dealloc];
1289 - (void) setText: (char *)text
1291   NSString *str = [NSString stringWithUTF8String: text];
1292   NSRect r  = [textField frame];
1293   NSSize tooltipDims;
1295   [textField setStringValue: str];
1296   tooltipDims = [[textField cell] cellSize];
1298   r.size.width = tooltipDims.width;
1299   r.size.height = tooltipDims.height;
1300   [textField setFrame: r];
1303 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1305   NSRect wr = [win frame];
1307   wr.origin = NSMakePoint (x, y);
1308   wr.size = [textField frame].size;
1310   [win setFrame: wr display: YES];
1311   [win orderFront: self];
1312   [win display];
1313   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1314                                          selector: @selector (hide)
1315                                          userInfo: nil repeats: NO];
1316   [timer retain];
1319 - (void) hide
1321   [win close];
1322   if (timer != nil)
1323     {
1324       if ([timer isValid])
1325         [timer invalidate];
1326       [timer release];
1327       timer = nil;
1328     }
1331 - (BOOL) isActive
1333   return timer != nil;
1336 - (NSRect) frame
1338   return [textField frame];
1341 @end  /* EmacsTooltip */
1345 /* ==========================================================================
1347     Popup Dialog: implementing functions
1349    ========================================================================== */
1351 struct Popdown_data
1353   NSAutoreleasePool *pool;
1354   EmacsDialogPanel *dialog;
1357 static Lisp_Object
1358 pop_down_menu (Lisp_Object arg)
1360   struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1361   struct Popdown_data *unwind_data = (struct Popdown_data *) p->pointer;
1363   BLOCK_INPUT;
1364   if (popup_activated_flag)
1365     {
1366       EmacsDialogPanel *panel = unwind_data->dialog;
1367       popup_activated_flag = 0;
1368       [NSApp endModalSession: popupSession];
1369       [panel close];
1370       [unwind_data->pool release];
1371       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1372     }
1374   xfree (unwind_data);
1375   UNBLOCK_INPUT;
1377   return Qnil;
1381 Lisp_Object
1382 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1384   id dialog;
1385   Lisp_Object window, tem, title;
1386   struct frame *f;
1387   NSPoint p;
1388   BOOL isQ;
1389   NSAutoreleasePool *pool;
1391   NSTRACE (x-popup-dialog);
1393   check_ns ();
1395   isQ = NILP (header);
1397   if (EQ (position, Qt)
1398       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1399                                || EQ (XCAR (position), Qtool_bar))))
1400     {
1401       window = selected_window;
1402     }
1403   else if (CONSP (position))
1404     {
1405       Lisp_Object tem;
1406       tem = Fcar (position);
1407       if (XTYPE (tem) == Lisp_Cons)
1408         window = Fcar (Fcdr (position));
1409       else
1410         {
1411           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1412           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1413         }
1414     }
1415   else if (WINDOWP (position) || FRAMEP (position))
1416     {
1417       window = position;
1418     }
1419   else
1420     window = Qnil;
1422   if (FRAMEP (window))
1423     f = XFRAME (window);
1424   else if (WINDOWP (window))
1425     {
1426       CHECK_LIVE_WINDOW (window);
1427       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1428     }
1429   else
1430     CHECK_WINDOW (window);
1432   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1433   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1435   title = Fcar (contents);
1436   CHECK_STRING (title);
1438   if (NILP (Fcar (Fcdr (contents))))
1439     /* No buttons specified, add an "Ok" button so users can pop down
1440        the dialog.  */
1441     contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1443   BLOCK_INPUT;
1444   pool = [[NSAutoreleasePool alloc] init];
1445   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1446                                            isQuestion: isQ];
1447   {
1448     int specpdl_count = SPECPDL_INDEX ();
1449     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1451     unwind_data->pool = pool;
1452     unwind_data->dialog = dialog;
1454     record_unwind_protect (pop_down_menu, make_save_value (unwind_data, 0));
1455     popup_activated_flag = 1;
1456     tem = [dialog runDialogAt: p];
1457     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1458   }
1459   UNBLOCK_INPUT;
1461   return tem;
1465 /* ==========================================================================
1467     Popup Dialog: class implementation
1469    ========================================================================== */
1471 @interface FlippedView : NSView
1474 @end
1476 @implementation FlippedView
1477 - (BOOL)isFlipped
1479   return YES;
1481 @end
1483 @implementation EmacsDialogPanel
1485 #define SPACER          8.0
1486 #define ICONSIZE        64.0
1487 #define TEXTHEIGHT      20.0
1488 #define MINCELLWIDTH    90.0
1490 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1491               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1493   NSSize spacing = {SPACER, SPACER};
1494   NSRect area;
1495   char this_cmd_name[80];
1496   id cell;
1497   NSImageView *imgView;
1498   FlippedView *contentView;
1499   NSImage *img;
1501   area.origin.x   = 3*SPACER;
1502   area.origin.y   = 2*SPACER;
1503   area.size.width = ICONSIZE;
1504   area.size.height= ICONSIZE;
1505   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1506   [img setScalesWhenResized: YES];
1507   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1508   imgView = [[NSImageView alloc] initWithFrame: area];
1509   [imgView setImage: img];
1510   [imgView setEditable: NO];
1511   [img autorelease];
1512   [imgView autorelease];
1514   aStyle = NSTitledWindowMask;
1515   flag = YES;
1516   rows = 0;
1517   cols = 1;
1518   [super initWithContentRect: contentRect styleMask: aStyle
1519                      backing: backingType defer: flag];
1520   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1521   [contentView autorelease];
1523   [self setContentView: contentView];
1525   [[self contentView] setAutoresizesSubviews: YES];
1527   [[self contentView] addSubview: imgView];
1528   [self setTitle: @""];
1530   area.origin.x   += ICONSIZE+2*SPACER;
1531 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1532   area.size.width = 400;
1533   area.size.height= TEXTHEIGHT;
1534   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1535   [[self contentView] addSubview: command];
1536   [command setStringValue: ns_app_name];
1537   [command setDrawsBackground: NO];
1538   [command setBezeled: NO];
1539   [command setSelectable: NO];
1540   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1542 /*  area.origin.x   = ICONSIZE+2*SPACER;
1543   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1544   area.size.width = 400;
1545   area.size.height= 2;
1546   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1547   [[self contentView] addSubview: tem];
1548   [tem setTitlePosition: NSNoTitle];
1549   [tem setAutoresizingMask: NSViewWidthSizable];*/
1551 /*  area.origin.x = ICONSIZE+2*SPACER; */
1552   area.origin.y += TEXTHEIGHT+SPACER;
1553   area.size.width = 400;
1554   area.size.height= TEXTHEIGHT;
1555   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1556   [[self contentView] addSubview: title];
1557   [title setDrawsBackground: NO];
1558   [title setBezeled: NO];
1559   [title setSelectable: NO];
1560   [title setFont: [NSFont systemFontOfSize: 11.0]];
1562   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1563   [cell setBordered: NO];
1564   [cell setEnabled: NO];
1565   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1566   [cell setBezelStyle: NSRoundedBezelStyle];
1568   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1569                                       mode: NSHighlightModeMatrix
1570                                  prototype: cell
1571                               numberOfRows: 0
1572                            numberOfColumns: 1];
1573   [[self contentView] addSubview: matrix];
1574   [matrix autorelease];
1575   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1576                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1577   [matrix setIntercellSpacing: spacing];
1579   [self setOneShot: YES];
1580   [self setReleasedWhenClosed: YES];
1581   [self setHidesOnDeactivate: YES];
1582   return self;
1586 - (BOOL)windowShouldClose: (id)sender
1588   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1589   return NO;
1593 void process_dialog (id window, Lisp_Object list)
1595   Lisp_Object item;
1596   int row = 0;
1598   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1599     {
1600       item = XCAR (list);
1601       if (XTYPE (item) == Lisp_String)
1602         {
1603           [window addString: SDATA (item) row: row++];
1604         }
1605       else if (XTYPE (item) == Lisp_Cons)
1606         {
1607           [window addButton: SDATA (XCAR (item))
1608                       value: XCDR (item) row: row++];
1609         }
1610       else if (NILP (item))
1611         {
1612           [window addSplit];
1613           row = 0;
1614         }
1615     }
1619 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1621   id cell;
1623   if (row >= rows)
1624     {
1625       [matrix addRow];
1626       rows++;
1627     }
1628   cell = [matrix cellAtRow: row column: cols-1];
1629   [cell setTarget: self];
1630   [cell setAction: @selector (clicked: )];
1631   [cell setTitle: [NSString stringWithUTF8String: str]];
1632   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1633   [cell setBordered: YES];
1634   [cell setEnabled: YES];
1636   return self;
1640 - addString: (char *)str row: (int)row
1642   id cell;
1644   if (row >= rows)
1645     {
1646       [matrix addRow];
1647       rows++;
1648     }
1649   cell = [matrix cellAtRow: row column: cols-1];
1650   [cell setTitle: [NSString stringWithUTF8String: str]];
1651   [cell setBordered: YES];
1652   [cell setEnabled: NO];
1654   return self;
1658 - addSplit
1660   [matrix addColumn];
1661   cols++;
1662   return self;
1666 - clicked: sender
1668   NSArray *sellist = nil;
1669   EMACS_INT seltag;
1671   sellist = [sender selectedCells];
1672   if ([sellist count]<1)
1673     return self;
1675   seltag = [[sellist objectAtIndex: 0] tag];
1676   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1677     [NSApp stopModalWithCode: seltag];
1678   return self;
1682 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1684   Lisp_Object head;
1685   [super init];
1687   if (XTYPE (contents) == Lisp_Cons)
1688     {
1689       head = Fcar (contents);
1690       process_dialog (self, Fcdr (contents));
1691     }
1692   else
1693     head = contents;
1695   if (XTYPE (head) == Lisp_String)
1696       [title setStringValue:
1697                  [NSString stringWithUTF8String: SDATA (head)]];
1698   else if (isQ == YES)
1699       [title setStringValue: @"Question"];
1700   else
1701       [title setStringValue: @"Information"];
1703   {
1704     int i;
1705     NSRect r, s, t;
1707     if (cols == 1 && rows > 1)  /* Never told where to split */
1708       {
1709         [matrix addColumn];
1710         for (i = 0; i<rows/2; i++)
1711           {
1712             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1713                       atRow: i column: 1];
1714             [matrix removeRow: (rows+1)/2];
1715           }
1716       }
1718     [matrix sizeToFit];
1719     {
1720       NSSize csize = [matrix cellSize];
1721       if (csize.width < MINCELLWIDTH)
1722         {
1723           csize.width = MINCELLWIDTH;
1724           [matrix setCellSize: csize];
1725           [matrix sizeToCells];
1726         }
1727     }
1729     [title sizeToFit];
1730     [command sizeToFit];
1732     t = [matrix frame];
1733     r = [title frame];
1734     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1735       {
1736         t.origin.x   = r.origin.x;
1737         t.size.width = r.size.width;
1738       }
1739     r = [command frame];
1740     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1741       {
1742         t.origin.x   = r.origin.x;
1743         t.size.width = r.size.width;
1744       }
1746     r = [self frame];
1747     s = [(NSView *)[self contentView] frame];
1748     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1749     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1750     [self setFrame: r display: NO];
1751   }
1753   return self;
1757 - (void)dealloc
1759   { [super dealloc]; return; };
1763 - (Lisp_Object)runDialogAt: (NSPoint)p
1765   NSInteger ret;
1767   /* initiate a session that will be ended by pop_down_menu */
1768   popupSession = [NSApp beginModalSessionForWindow: self];
1769   while (popup_activated_flag
1770          && (ret = [NSApp runModalSession: popupSession])
1771               == NSRunContinuesResponse)
1772     {
1773       /* Run this for timers.el, indep of atimers; might not return.
1774          TODO: use return value to avoid calling every iteration. */
1775       timer_check ();
1776       [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1777     }
1779   {                             /* FIXME: BIG UGLY HACK!!! */
1780       Lisp_Object tmp;
1781       *(EMACS_INT*)(&tmp) = ret;
1782       return tmp;
1783   }
1786 @end
1789 /* ==========================================================================
1791     Lisp definitions
1793    ========================================================================== */
1795 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1796        doc: /* Cause the NS menu to be re-calculated.  */)
1797      (void)
1799   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1800   return Qnil;
1804 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1805        doc: /* Pop up a dialog box and return user's selection.
1806 POSITION specifies which frame to use.
1807 This is normally a mouse button event or a window or frame.
1808 If POSITION is t, it means to use the frame the mouse is on.
1809 The dialog box appears in the middle of the specified frame.
1811 CONTENTS specifies the alternatives to display in the dialog box.
1812 It is a list of the form (DIALOG ITEM1 ITEM2...).
1813 Each ITEM is a cons cell (STRING . VALUE).
1814 The return value is VALUE from the chosen item.
1816 An ITEM may also be just a string--that makes a nonselectable item.
1817 An ITEM may also be nil--that means to put all preceding items
1818 on the left of the dialog box and all following items on the right.
1819 \(By default, approximately half appear on each side.)
1821 If HEADER is non-nil, the frame title for the box is "Information",
1822 otherwise it is "Question".
1824 If the user gets rid of the dialog box without making a valid choice,
1825 for instance using the window manager, then this produces a quit and
1826 `x-popup-dialog' does not return.  */)
1827      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1829   return ns_popup_dialog (position, contents, header);
1832 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1833        doc: /* Return t if a menu or popup dialog is active.  */)
1834      (void)
1836   return popup_activated () ? Qt : Qnil;
1839 /* ==========================================================================
1841     Lisp interface declaration
1843    ========================================================================== */
1845 void
1846 syms_of_nsmenu (void)
1848 #ifndef NS_IMPL_COCOA
1849   /* Don't know how to keep track of this in Next/Open/Gnustep.  Always
1850      update menus there.  */
1851   trackingMenu = 1;
1852 #endif
1853   defsubr (&Sx_popup_dialog);
1854   defsubr (&Sns_reset_menu);
1855   defsubr (&Smenu_or_popup_active_p);
1857   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1858   staticpro (&Qdebug_on_next_call);