* lisp/emacs-lisp/pcase.el (pcase--expand): Warn for unused pattern.
[emacs.git] / src / nsmenu.m
blob1f86417b53ad67e7fff2f3f3349b6eecd05caab0
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 "character.h"
32 #include "buffer.h"
33 #include "keymap.h"
34 #include "coding.h"
35 #include "commands.h"
36 #include "blockinput.h"
37 #include "nsterm.h"
38 #include "termhooks.h"
39 #include "keyboard.h"
40 #include "menu.h"
42 #define NSMENUPROFILE 0
44 #if NSMENUPROFILE
45 #include <sys/timeb.h>
46 #include <sys/types.h>
47 #endif
49 #define MenuStagger 10.0
51 #if 0
52 int menu_trace_num = 0;
53 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
54                                 __FILE__, __LINE__, ++menu_trace_num)
55 #else
56 #define NSTRACE(x)
57 #endif
59 #if 0
60 /* Include lisp -> C common menu parsing code */
61 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
62 #include "nsmenu_common.c"
63 #endif
65 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
66 extern Lisp_Object QCtoggle, QCradio;
68 Lisp_Object Qdebug_on_next_call;
69 extern Lisp_Object Qoverriding_local_map, Qoverriding_terminal_local_map;
71 extern long context_menu_value;
72 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
74 /* Nonzero means a menu is currently active.  */
75 static int popup_activated_flag;
76 static NSModalSession popupSession;
78 /* Nonzero means we are tracking and updating menus.  */
79 static int trackingMenu;
82 /* NOTE: toolbar implementation is at end,
83   following complete menu implementation. */
86 /* ==========================================================================
88     Menu: Externally-called functions
90    ========================================================================== */
93 /* FIXME: not currently used, but should normalize with other terms. */
94 void
95 x_activate_menubar (struct frame *f)
97     fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
101 /* Supposed to discard menubar and free storage.  Since we share the
102    menubar among frames and update its context for the focused window,
103    there is nothing to do here. */
104 void
105 free_frame_menubar (struct frame *f)
107   return;
112 popup_activated (void)
114   return popup_activated_flag;
118 /* --------------------------------------------------------------------------
119     Update menubar.  Three cases:
120     1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
121        just top-level menu strings (OS X), or goto case (2) (GNUstep).
122     2) deep_p = 1, submenu = nil: Recompute all submenus.
123     3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
124    -------------------------------------------------------------------------- */
125 void
126 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
128   NSAutoreleasePool *pool;
129   id menu = [NSApp mainMenu];
130   static EmacsMenu *last_submenu = nil;
131   BOOL needsSet = NO;
132   const char *submenuTitle = [[submenu title] UTF8String];
133   extern int waiting_for_input;
134   int owfi;
135   Lisp_Object items;
136   widget_value *wv, *first_wv, *prev_wv = 0;
137   int i;
139 #if NSMENUPROFILE
140   struct timeb tb;
141   long t;
142 #endif
144   NSTRACE (set_frame_menubar);
146   if (f != SELECTED_FRAME ())
147       return;
148   XSETFRAME (Vmenu_updating_frame, f);
149 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
151   BLOCK_INPUT;
152   pool = [[NSAutoreleasePool alloc] init];
154   /* Menu may have been created automatically; if so, discard it. */
155   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
156     {
157       [menu release];
158       menu = nil;
159     }
161   if (menu == nil)
162     {
163       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
164       needsSet = YES;
165     }
166   else
167     {  /* close up anything on there */
168       id attMenu = [menu attachedMenu];
169       if (attMenu != nil)
170         [attMenu close];
171     }
173 #if NSMENUPROFILE
174   ftime (&tb);
175   t = -(1000*tb.time+tb.millitm);
176 #endif
178 #ifdef NS_IMPL_GNUSTEP
179   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
180 #endif
182   if (deep_p)
183     {
184       /* Fully parse one or more of the submenus. */
185       int n = 0;
186       int *submenu_start, *submenu_end;
187       int *submenu_top_level_items, *submenu_n_panes;
188       struct buffer *prev = current_buffer;
189       Lisp_Object buffer;
190       ptrdiff_t specpdl_count = SPECPDL_INDEX ();
191       int previous_menu_items_used = f->menu_bar_items_used;
192       Lisp_Object *previous_items
193         = (Lisp_Object *) alloca (previous_menu_items_used
194                                   * sizeof (Lisp_Object));
196       /* lisp preliminaries */
197       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
198       specbind (Qinhibit_quit, Qt);
199       specbind (Qdebug_on_next_call, Qnil);
200       record_unwind_save_match_data ();
201       if (NILP (Voverriding_local_map_menu_flag))
202         {
203           specbind (Qoverriding_terminal_local_map, Qnil);
204           specbind (Qoverriding_local_map, Qnil);
205         }
206       set_buffer_internal_1 (XBUFFER (buffer));
208       /* TODO: for some reason this is not needed in other terms,
209            but some menu updates call Info-extract-pointer which causes
210            abort-on-error if waiting-for-input.  Needs further investigation. */
211       owfi = waiting_for_input;
212       waiting_for_input = 0;
214       /* lucid hook and possible reset */
215       safe_run_hooks (Qactivate_menubar_hook);
216       if (! NILP (Vlucid_menu_bar_dirty_flag))
217         call0 (Qrecompute_lucid_menubar);
218       safe_run_hooks (Qmenu_bar_update_hook);
219       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
221       /* Now ready to go */
222       items = FRAME_MENU_BAR_ITEMS (f);
224       /* Save the frame's previous menu bar contents data */
225       if (previous_menu_items_used)
226         memcpy (previous_items, &AREF (f->menu_bar_vector, 0),
227                 previous_menu_items_used * sizeof (Lisp_Object));
229       /* parse stage 1: extract from lisp */
230       save_menu_items ();
232       menu_items = f->menu_bar_vector;
233       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
234       submenu_start = (int *) alloca (ASIZE (items) * sizeof (int *));
235       submenu_end = (int *) alloca (ASIZE (items) * sizeof (int *));
236       submenu_n_panes = (int *) alloca (ASIZE (items) * sizeof (int));
237       submenu_top_level_items
238         = (int *) alloca (ASIZE (items) * sizeof (int *));
239       init_menu_items ();
240       for (i = 0; i < ASIZE (items); i += 4)
241         {
242           Lisp_Object key, string, maps;
244           key = AREF (items, i);
245           string = AREF (items, i + 1);
246           maps = AREF (items, i + 2);
247           if (NILP (string))
248             break;
250           /* FIXME: we'd like to only parse the needed submenu, but this
251                was causing crashes in the _common parsing code.. need to make
252                sure proper initialization done.. */
253 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
254              continue; */
256           submenu_start[i] = menu_items_used;
258           menu_items_n_panes = 0;
259           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
260           submenu_n_panes[i] = menu_items_n_panes;
261           submenu_end[i] = menu_items_used;
262           n++;
263         }
265       finish_menu_items ();
266       waiting_for_input = owfi;
269       if (submenu && n == 0)
270         {
271           /* should have found a menu for this one but didn't */
272           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
273                   submenuTitle);
274           discard_menu_items ();
275           unbind_to (specpdl_count, Qnil);
276           [pool release];
277           UNBLOCK_INPUT;
278           return;
279         }
281       /* parse stage 2: insert into lucid 'widget_value' structures
282          [comments in other terms say not to evaluate lisp code here] */
283       wv = xmalloc_widget_value ();
284       wv->name = "menubar";
285       wv->value = 0;
286       wv->enabled = 1;
287       wv->button_type = BUTTON_TYPE_NONE;
288       wv->help = Qnil;
289       first_wv = wv;
291       for (i = 0; i < 4*n; i += 4)
292         {
293           menu_items_n_panes = submenu_n_panes[i];
294           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
295                                       submenu_top_level_items[i]);
296           if (prev_wv)
297             prev_wv->next = wv;
298           else
299             first_wv->contents = wv;
300           /* Don't set wv->name here; GC during the loop might relocate it.  */
301           wv->enabled = 1;
302           wv->button_type = BUTTON_TYPE_NONE;
303           prev_wv = wv;
304         }
306       set_buffer_internal_1 (prev);
308       /* Compare the new menu items with previous, and leave off if no change */
309       /* FIXME: following other terms here, but seems like this should be
310            done before parse stage 2 above, since its results aren't used */
311       if (previous_menu_items_used
312           && (!submenu || (submenu && submenu == last_submenu))
313           && menu_items_used == previous_menu_items_used)
314         {
315           for (i = 0; i < previous_menu_items_used; i++)
316             /* FIXME: this ALWAYS fails on Buffers menu items.. something
317                  about their strings causes them to change every time, so we
318                  double-check failures */
319             if (!EQ (previous_items[i], AREF (menu_items, i)))
320               if (!(STRINGP (previous_items[i])
321                     && STRINGP (AREF (menu_items, i))
322                     && !strcmp (SDATA (previous_items[i]),
323                                 SDATA (AREF (menu_items, i)))))
324                   break;
325           if (i == previous_menu_items_used)
326             {
327               /* No change.. */
329 #if NSMENUPROFILE
330               ftime (&tb);
331               t += 1000*tb.time+tb.millitm;
332               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
333 #endif
335               free_menubar_widget_value_tree (first_wv);
336               discard_menu_items ();
337               unbind_to (specpdl_count, Qnil);
338               [pool release];
339               UNBLOCK_INPUT;
340               return;
341             }
342         }
343       /* The menu items are different, so store them in the frame */
344       /* FIXME: this is not correct for single-submenu case */
345       f->menu_bar_vector = menu_items;
346       f->menu_bar_items_used = menu_items_used;
348       /* Calls restore_menu_items, etc., as they were outside */
349       unbind_to (specpdl_count, Qnil);
351       /* Parse stage 2a: now GC cannot happen during the lifetime of the
352          widget_value, so it's safe to store data from a Lisp_String */
353       wv = first_wv->contents;
354       for (i = 0; i < ASIZE (items); i += 4)
355         {
356           Lisp_Object string;
357           string = AREF (items, i + 1);
358           if (NILP (string))
359             break;
360 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
361                continue; */
363           wv->name = SSDATA (string);
364           update_submenu_strings (wv->contents);
365           wv = wv->next;
366         }
368       /* Now, update the NS menu; if we have a submenu, use that, otherwise
369          create a new menu for each sub and fill it. */
370       if (submenu)
371         {
372           for (wv = first_wv->contents; wv; wv = wv->next)
373             {
374               if (!strcmp (submenuTitle, wv->name))
375                 {
376                   [submenu fillWithWidgetValue: wv->contents];
377                   last_submenu = submenu;
378                   break;
379                 }
380             }
381         }
382       else
383         {
384           [menu fillWithWidgetValue: first_wv->contents];
385         }
387     }
388   else
389     {
390       static int n_previous_strings = 0;
391       static char previous_strings[100][10];
392       static struct frame *last_f = NULL;
393       int n;
394       Lisp_Object string;
396       wv = xmalloc_widget_value ();
397       wv->name = "menubar";
398       wv->value = 0;
399       wv->enabled = 1;
400       wv->button_type = BUTTON_TYPE_NONE;
401       wv->help = Qnil;
402       first_wv = wv;
404       /* Make widget-value tree w/ just the top level menu bar strings */
405       items = FRAME_MENU_BAR_ITEMS (f);
406       if (NILP (items))
407         {
408           free_menubar_widget_value_tree (first_wv);
409           [pool release];
410           UNBLOCK_INPUT;
411           return;
412         }
415       /* check if no change.. this mechanism is a bit rough, but ready */
416       n = ASIZE (items) / 4;
417       if (f == last_f && n_previous_strings == n)
418         {
419           for (i = 0; i<n; i++)
420             {
421               string = AREF (items, 4*i+1);
423               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
424                 continue;
425               if (NILP (string))
426                 if (previous_strings[i][0])
427                   break;
428               else
429                 continue;
430               if (strncmp (previous_strings[i], SDATA (string), 10))
431                 break;
432             }
434           if (i == n)
435             {
436               free_menubar_widget_value_tree (first_wv);
437               [pool release];
438               UNBLOCK_INPUT;
439               return;
440             }
441         }
443       [menu clear];
444       for (i = 0; i < ASIZE (items); i += 4)
445         {
446           string = AREF (items, i + 1);
447           if (NILP (string))
448             break;
450           if (n < 100)
451             strncpy (previous_strings[i/4], SDATA (string), 10);
453           wv = xmalloc_widget_value ();
454           wv->name = SSDATA (string);
455           wv->value = 0;
456           wv->enabled = 1;
457           wv->button_type = BUTTON_TYPE_NONE;
458           wv->help = Qnil;
459           wv->call_data = (void *) (intptr_t) (-1);
461 #ifdef NS_IMPL_COCOA
462           /* we'll update the real copy under app menu when time comes */
463           if (!strcmp ("Services", wv->name))
464             {
465               /* but we need to make sure it will update on demand */
466               [svcsMenu setFrame: f];
467             }
468           else
469 #endif
470           [menu addSubmenuWithTitle: wv->name forFrame: f];
472           if (prev_wv)
473             prev_wv->next = wv;
474           else
475             first_wv->contents = wv;
476           prev_wv = wv;
477         }
479       last_f = f;
480       if (n < 100)
481         n_previous_strings = n;
482       else
483         n_previous_strings = 0;
485     }
486   free_menubar_widget_value_tree (first_wv);
489 #if NSMENUPROFILE
490   ftime (&tb);
491   t += 1000*tb.time+tb.millitm;
492   fprintf (stderr, "Menu update took %ld msec.\n", t);
493 #endif
495   /* set main menu */
496   if (needsSet)
497     [NSApp setMainMenu: menu];
499   [pool release];
500   UNBLOCK_INPUT;
505 /* Main emacs core entry point for menubar menus: called to indicate that the
506    frame's menus have changed, and the *step representation should be updated
507    from Lisp. */
508 void
509 set_frame_menubar (struct frame *f, int first_time, int deep_p)
511   ns_update_menubar (f, deep_p, nil);
515 /* ==========================================================================
517     Menu: class implementation
519    ========================================================================== */
522 /* Menu that can define itself from Emacs "widget_value"s and will lazily
523    update itself when user clicked.  Based on Carbon/AppKit implementation
524    by Yamamoto Mitsuharu. */
525 @implementation EmacsMenu
527 /* override designated initializer */
528 - initWithTitle: (NSString *)title
530   if (self = [super initWithTitle: title])
531     [self setAutoenablesItems: NO];
532   return self;
536 /* used for top-level */
537 - initWithTitle: (NSString *)title frame: (struct frame *)f
539   [self initWithTitle: title];
540   frame = f;
541 #ifdef NS_IMPL_COCOA
542   [self setDelegate: self];
543 #endif
544   return self;
548 - (void)setFrame: (struct frame *)f
550   frame = f;
553 #ifdef NS_IMPL_COCOA
554 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
555 extern NSString *NSMenuDidBeginTrackingNotification;
556 #endif
557 #endif
559 #ifdef NS_IMPL_COCOA
560 -(void)trackingNotification:(NSNotification *)notification
562   /* Update menu in menuNeedsUpdate only while tracking menus.  */
563   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
564                   ? 1 : 0);
566 #endif
568 /* delegate method called when a submenu is being opened: run a 'deep' call
569    to set_frame_menubar */
570 - (void)menuNeedsUpdate: (NSMenu *)menu
572   if (!FRAME_LIVE_P (frame))
573     return;
575   /* Cocoa/Carbon will request update on every keystroke
576      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
577      since key equivalents are handled through emacs.
578      On Leopard, even keystroke events generate SystemDefined event.
579      Third-party applications that enhance mouse / trackpad
580      interaction, or also VNC/Remote Desktop will send events
581      of type AppDefined rather than SysDefined.
582      Menus will fail to show up if they haven't been initialized.
583      AppDefined events may lack timing data.
585      Thus, we rely on the didBeginTrackingNotification notification
586      as above to indicate the need for updates.
587      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
588      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
589   */
590   if (trackingMenu == 0
591       /* Also, don't try this if from an event picked up asynchronously,
592          as lots of lisp evaluation happens in ns_update_menubar. */
593       || handling_signal != 0)
594     return;
595 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
596   ns_update_menubar (frame, 1, self);
600 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
602   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
603       && FRAME_NS_VIEW (SELECTED_FRAME ()))
604     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
605   return YES;
609 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
610    into an accelerator string.  We are only able to display a single character
611    for an accelerator, together with an optional modifier combination.  (Under
612    Carbon more control was possible, but in Cocoa multi-char strings passed to
613    NSMenuItem get ignored.  For now we try to display a super-single letter
614    combo, and return the others as strings to be appended to the item title.
615    (This is signaled by setting keyEquivModMask to 0 for now.) */
616 -(NSString *)parseKeyEquiv: (const char *)key
618   const char *tpos = key;
619   keyEquivModMask = NSCommandKeyMask;
621   if (!key || !strlen (key))
622     return @"";
624   while (*tpos == ' ' || *tpos == '(')
625     tpos++;
626   if ((*tpos == 's') && (*(tpos+1) == '-'))
627     {
628       return [NSString stringWithFormat: @"%c", tpos[2]];
629     }
630   keyEquivModMask = 0; /* signal */
631   return [NSString stringWithUTF8String: tpos];
635 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
637   NSMenuItem *item;
638   widget_value *wv = (widget_value *)wvptr;
640   if (menu_separator_name_p (wv->name))
641     {
642       item = [NSMenuItem separatorItem];
643       [self addItem: item];
644     }
645   else
646     {
647       NSString *title, *keyEq;
648       title = [NSString stringWithUTF8String: wv->name];
649       if (title == nil)
650         title = @"< ? >";  /* (get out in the open so we know about it) */
652       keyEq = [self parseKeyEquiv: wv->key];
653 #ifdef NS_IMPL_COCOA
654       /* OS X just ignores modifier strings longer than one character */
655       if (keyEquivModMask == 0)
656         title = [title stringByAppendingFormat: @" (%@)", keyEq];
657 #endif
659       item = [self addItemWithTitle: (NSString *)title
660                              action: @selector (menuDown:)
661                       keyEquivalent: keyEq];
662       [item setKeyEquivalentModifierMask: keyEquivModMask];
664       [item setEnabled: wv->enabled];
666       /* Draw radio buttons and tickboxes */
667       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
668                            wv->button_type == BUTTON_TYPE_RADIO))
669         [item setState: NSOnState];
670       else
671         [item setState: NSOffState];
673       [item setTag: (NSInteger)wv->call_data];
674     }
676   return item;
680 /* convenience */
681 -(void)clear
683   int n;
685   for (n = [self numberOfItems]-1; n >= 0; n--)
686     {
687       NSMenuItem *item = [self itemAtIndex: n];
688       NSString *title = [item title];
689       if (([title length] == 0  /* OSX 10.5 */
690            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
691            || [@"Apple" isEqualToString: title]) /* older */
692           && ![item isSeparatorItem])
693         continue;
694       [self removeItemAtIndex: n];
695     }
699 - (void)fillWithWidgetValue: (void *)wvptr
701   widget_value *wv = (widget_value *)wvptr;
703   /* clear existing contents */
704   [self setMenuChangedMessagesEnabled: NO];
705   [self clear];
707   /* add new contents */
708   for (; wv != NULL; wv = wv->next)
709     {
710       NSMenuItem *item = [self addItemWithWidgetValue: wv];
712       if (wv->contents)
713         {
714           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
716           [self setSubmenu: submenu forItem: item];
717           [submenu fillWithWidgetValue: wv->contents];
718           [submenu release];
719           [item setAction: nil];
720         }
721     }
723   [self setMenuChangedMessagesEnabled: YES];
724 #ifdef NS_IMPL_GNUSTEP
725   if ([[self window] isVisible])
726     [self sizeToFit];
727 #else
728 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_2
729   if ([self supermenu] == nil)
730     [self sizeToFit];
731 #endif
732 #endif
736 /* adds an empty submenu and returns it */
737 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
739   NSString *titleStr = [NSString stringWithUTF8String: title];
740   NSMenuItem *item = [self addItemWithTitle: titleStr
741                                      action: nil /*@selector (menuDown:) */
742                               keyEquivalent: @""];
743   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
744   [self setSubmenu: submenu forItem: item];
745   [submenu release];
746   return submenu;
749 /* run a menu in popup mode */
750 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
751                  keymaps: (int)keymaps
753   EmacsView *view = FRAME_NS_VIEW (f);
754   NSEvent *e, *event;
755   long retVal;
757 /*   p = [view convertPoint:p fromView: nil]; */
758   p.y = NSHeight ([view frame]) - p.y;
759   e = [[view window] currentEvent];
760    event = [NSEvent mouseEventWithType: NSRightMouseDown
761                               location: p
762                          modifierFlags: 0
763                              timestamp: [e timestamp]
764                           windowNumber: [[view window] windowNumber]
765                                context: [e context]
766                            eventNumber: 0/*[e eventNumber] */
767                             clickCount: 1
768                               pressure: 0];
770   context_menu_value = -1;
771   [NSMenu popUpContextMenu: self withEvent: event forView: view];
772   retVal = context_menu_value;
773   context_menu_value = 0;
774   return retVal > 0
775       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
776       : Qnil;
779 @end  /* EmacsMenu */
783 /* ==========================================================================
785     Context Menu: implementing functions
787    ========================================================================== */
789 Lisp_Object
790 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
791               Lisp_Object title, const char **error)
793   EmacsMenu *pmenu;
794   NSPoint p;
795   Lisp_Object window, tem, keymap;
796   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
797   widget_value *wv, *first_wv = 0;
799   p.x = x; p.y = y;
801   /* now parse stage 2 as in ns_update_menubar */
802   wv = xmalloc_widget_value ();
803   wv->name = "contextmenu";
804   wv->value = 0;
805   wv->enabled = 1;
806   wv->button_type = BUTTON_TYPE_NONE;
807   wv->help = Qnil;
808   first_wv = wv;
810 #if 0
811   /* FIXME: a couple of one-line differences prevent reuse */
812   wv = digest_single_submenu (0, menu_items_used, Qnil);
813 #else
814   {
815   widget_value *save_wv = 0, *prev_wv = 0;
816   widget_value **submenu_stack
817     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
818 /*   Lisp_Object *subprefix_stack
819        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
820   int submenu_depth = 0;
821   int first_pane = 1;
822   int i;
824   /* Loop over all panes and items, filling in the tree.  */
825   i = 0;
826   while (i < menu_items_used)
827     {
828       if (EQ (AREF (menu_items, i), Qnil))
829         {
830           submenu_stack[submenu_depth++] = save_wv;
831           save_wv = prev_wv;
832           prev_wv = 0;
833           first_pane = 1;
834           i++;
835         }
836       else if (EQ (AREF (menu_items, i), Qlambda))
837         {
838           prev_wv = save_wv;
839           save_wv = submenu_stack[--submenu_depth];
840           first_pane = 0;
841           i++;
842         }
843       else if (EQ (AREF (menu_items, i), Qt)
844                && submenu_depth != 0)
845         i += MENU_ITEMS_PANE_LENGTH;
846       /* Ignore a nil in the item list.
847          It's meaningful only for dialog boxes.  */
848       else if (EQ (AREF (menu_items, i), Qquote))
849         i += 1;
850       else if (EQ (AREF (menu_items, i), Qt))
851         {
852           /* Create a new pane.  */
853           Lisp_Object pane_name, prefix;
854           const char *pane_string;
856           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
857           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
859 #ifndef HAVE_MULTILINGUAL_MENU
860           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
861             {
862               pane_name = ENCODE_MENU_STRING (pane_name);
863               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
864             }
865 #endif
866           pane_string = (NILP (pane_name)
867                          ? "" : SSDATA (pane_name));
868           /* If there is just one top-level pane, put all its items directly
869              under the top-level menu.  */
870           if (menu_items_n_panes == 1)
871             pane_string = "";
873           /* If the pane has a meaningful name,
874              make the pane a top-level menu item
875              with its items as a submenu beneath it.  */
876           if (!keymaps && strcmp (pane_string, ""))
877             {
878               wv = xmalloc_widget_value ();
879               if (save_wv)
880                 save_wv->next = wv;
881               else
882                 first_wv->contents = wv;
883               wv->name = pane_string;
884               if (keymaps && !NILP (prefix))
885                 wv->name++;
886               wv->value = 0;
887               wv->enabled = 1;
888               wv->button_type = BUTTON_TYPE_NONE;
889               wv->help = Qnil;
890               save_wv = wv;
891               prev_wv = 0;
892             }
893           else if (first_pane)
894             {
895               save_wv = wv;
896               prev_wv = 0;
897             }
898           first_pane = 0;
899           i += MENU_ITEMS_PANE_LENGTH;
900         }
901       else
902         {
903           /* Create a new item within current pane.  */
904           Lisp_Object item_name, enable, descrip, def, type, selected, help;
905           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
906           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
907           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
908           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
909           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
910           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
911           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
913 #ifndef HAVE_MULTILINGUAL_MENU
914           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
915             {
916               item_name = ENCODE_MENU_STRING (item_name);
917               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
918             }
920           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
921             {
922               descrip = ENCODE_MENU_STRING (descrip);
923               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
924             }
925 #endif /* not HAVE_MULTILINGUAL_MENU */
927           wv = xmalloc_widget_value ();
928           if (prev_wv)
929             prev_wv->next = wv;
930           else
931             save_wv->contents = wv;
932           wv->name = SSDATA (item_name);
933           if (!NILP (descrip))
934             wv->key = SSDATA (descrip);
935           wv->value = 0;
936           /* If this item has a null value,
937              make the call_data null so that it won't display a box
938              when the mouse is on it.  */
939           wv->call_data
940               = !NILP (def) ? (void *) &AREF (menu_items, i) : 0;
941           wv->enabled = !NILP (enable);
943           if (NILP (type))
944             wv->button_type = BUTTON_TYPE_NONE;
945           else if (EQ (type, QCtoggle))
946             wv->button_type = BUTTON_TYPE_TOGGLE;
947           else if (EQ (type, QCradio))
948             wv->button_type = BUTTON_TYPE_RADIO;
949           else
950             abort ();
952           wv->selected = !NILP (selected);
954           if (! STRINGP (help))
955             help = Qnil;
957           wv->help = help;
959           prev_wv = wv;
961           i += MENU_ITEMS_ITEM_LENGTH;
962         }
963     }
964   }
965 #endif
967   if (!NILP (title))
968     {
969       widget_value *wv_title = xmalloc_widget_value ();
970       widget_value *wv_sep = xmalloc_widget_value ();
972       /* Maybe replace this separator with a bitmap or owner-draw item
973          so that it looks better.  Having two separators looks odd.  */
974       wv_sep->name = "--";
975       wv_sep->next = first_wv->contents;
976       wv_sep->help = Qnil;
978 #ifndef HAVE_MULTILINGUAL_MENU
979       if (STRING_MULTIBYTE (title))
980         title = ENCODE_MENU_STRING (title);
981 #endif
983       wv_title->name = SSDATA (title);
984       wv_title->enabled = NO;
985       wv_title->button_type = BUTTON_TYPE_NONE;
986       wv_title->help = Qnil;
987       wv_title->next = wv_sep;
988       first_wv->contents = wv_title;
989     }
991   pmenu = [[EmacsMenu alloc] initWithTitle:
992                                [NSString stringWithUTF8String: SDATA (title)]];
993   [pmenu fillWithWidgetValue: first_wv->contents];
994   free_menubar_widget_value_tree (first_wv);
995   unbind_to (specpdl_count, Qnil);
997   popup_activated_flag = 1;
998   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
999   popup_activated_flag = 0;
1000   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1002   return tem;
1006 /* ==========================================================================
1008     Toolbar: externally-called functions
1010    ========================================================================== */
1012 void
1013 free_frame_tool_bar (FRAME_PTR f)
1014 /* --------------------------------------------------------------------------
1015     Under NS we just hide the toolbar until it might be needed again.
1016    -------------------------------------------------------------------------- */
1018   BLOCK_INPUT;
1019   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1020   FRAME_TOOLBAR_HEIGHT (f) = 0;
1021   UNBLOCK_INPUT;
1024 void
1025 update_frame_tool_bar (FRAME_PTR f)
1026 /* --------------------------------------------------------------------------
1027     Update toolbar contents
1028    -------------------------------------------------------------------------- */
1030   int i;
1031   EmacsView *view = FRAME_NS_VIEW (f);
1032   NSWindow *window = [view window];
1033   EmacsToolbar *toolbar = [view toolbar];
1035   BLOCK_INPUT;
1036   [toolbar clearActive];
1038   /* update EmacsToolbar as in GtkUtils, build items list */
1039   for (i = 0; i < f->n_tool_bar_items; ++i)
1040     {
1041 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1042                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1044       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1045       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1046       int idx;
1047       ptrdiff_t img_id;
1048       struct image *img;
1049       Lisp_Object image;
1050       Lisp_Object helpObj;
1051       const char *helpText;
1053       /* If image is a vector, choose the image according to the
1054          button state.  */
1055       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1056       if (VECTORP (image))
1057         {
1058           /* NS toolbar auto-computes disabled and selected images */
1059           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1060           xassert (ASIZE (image) >= idx);
1061           image = AREF (image, idx);
1062         }
1063       else
1064         {
1065           idx = -1;
1066         }
1067       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1068       if (NILP (helpObj))
1069         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1070       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1072       /* Ignore invalid image specifications.  */
1073       if (!valid_image_p (image))
1074         {
1075           /* Don't log anything, GNUS makes invalid images all the time.  */
1076           continue;
1077         }
1079       img_id = lookup_image (f, image);
1080       img = IMAGE_FROM_ID (f, img_id);
1081       prepare_image_for_display (f, img);
1083       if (img->load_failed_p || img->pixmap == nil)
1084         {
1085           NSLog (@"Could not prepare toolbar image for display.");
1086           continue;
1087         }
1089       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1090                                enabled: enabled_p];
1091 #undef TOOLPROP
1092     }
1094   if (![toolbar isVisible])
1095       [toolbar setVisible: YES];
1097   if ([toolbar changed])
1098     {
1099       /* inform app that toolbar has changed */
1100       NSDictionary *dict = [toolbar configurationDictionary];
1101       NSMutableDictionary *newDict = [dict mutableCopy];
1102       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1103       NSObject *key;
1104       while ((key = [keys nextObject]) != nil)
1105         {
1106           NSObject *val = [dict objectForKey: key];
1107           if ([val isKindOfClass: [NSArray class]])
1108             {
1109               [newDict setObject:
1110                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1111                           forKey: key];
1112               break;
1113             }
1114         }
1115       [toolbar setConfigurationFromDictionary: newDict];
1116       [newDict release];
1117     }
1119   FRAME_TOOLBAR_HEIGHT (f) =
1120     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1121     - FRAME_NS_TITLEBAR_HEIGHT (f);
1122   UNBLOCK_INPUT;
1126 /* ==========================================================================
1128     Toolbar: class implementation
1130    ========================================================================== */
1132 @implementation EmacsToolbar
1134 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1136   self = [super initWithIdentifier: identifier];
1137   emacsView = view;
1138   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1139   [self setSizeMode: NSToolbarSizeModeSmall];
1140   [self setDelegate: self];
1141   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1142   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1143   prevEnablement = enablement = 0L;
1144   return self;
1147 - (void)dealloc
1149   [prevIdentifiers release];
1150   [activeIdentifiers release];
1151   [identifierToItem release];
1152   [super dealloc];
1155 - (void) clearActive
1157   [prevIdentifiers release];
1158   prevIdentifiers = [activeIdentifiers copy];
1159   [activeIdentifiers removeAllObjects];
1160   prevEnablement = enablement;
1161   enablement = 0L;
1164 - (BOOL) changed
1166   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1167     enablement == prevEnablement ? NO : YES;
1170 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1171                         helpText: (const char *)help enabled: (BOOL)enabled
1173   /* 1) come up w/identifier */
1174   NSString *identifier
1175       = [NSString stringWithFormat: @"%u", [img hash]];
1177   /* 2) create / reuse item */
1178   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1179   if (item == nil)
1180     {
1181       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1182                autorelease];
1183       [item setImage: img];
1184       [item setToolTip: [NSString stringWithUTF8String: help]];
1185       [item setTarget: emacsView];
1186       [item setAction: @selector (toolbarClicked:)];
1187     }
1189   [item setTag: idx];
1190   [item setEnabled: enabled];
1192   /* 3) update state */
1193   [identifierToItem setObject: item forKey: identifier];
1194   [activeIdentifiers addObject: identifier];
1195   enablement = (enablement << 1) | (enabled == YES);
1198 /* This overrides super's implementation, which automatically sets
1199    all items to enabled state (for some reason). */
1200 - (void)validateVisibleItems { }
1203 /* delegate methods */
1205 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1206       itemForItemIdentifier: (NSString *)itemIdentifier
1207   willBeInsertedIntoToolbar: (BOOL)flag
1209   /* look up NSToolbarItem by identifier and return... */
1210   return [identifierToItem objectForKey: itemIdentifier];
1213 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1215   /* return entire set.. */
1216   return activeIdentifiers;
1219 /* for configuration palette (not yet supported) */
1220 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1222   /* return entire set... */
1223   return [identifierToItem allKeys];
1226 /* optional and unneeded */
1227 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1228 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1229 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1231 @end  /* EmacsToolbar */
1235 /* ==========================================================================
1237     Tooltip: class implementation
1239    ========================================================================== */
1241 /* Needed because NeXTstep does not provide enough control over tooltip
1242    display. */
1243 @implementation EmacsTooltip
1245 - init
1247   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1248                                             blue: 0.792 alpha: 0.95];
1249   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1250   NSFont *sfont = [font screenFont];
1251   int height = [sfont ascender] - [sfont descender];
1252 /*[font boundingRectForFont].size.height; */
1253   NSRect r = NSMakeRect (0, 0, 100, height+6);
1255   textField = [[NSTextField alloc] initWithFrame: r];
1256   [textField setFont: font];
1257   [textField setBackgroundColor: col];
1259   [textField setEditable: NO];
1260   [textField setSelectable: NO];
1261   [textField setBordered: NO];
1262   [textField setBezeled: NO];
1263   [textField setDrawsBackground: YES];
1265   win = [[NSWindow alloc]
1266             initWithContentRect: [textField frame]
1267                       styleMask: 0
1268                         backing: NSBackingStoreBuffered
1269                           defer: YES];
1270   [win setHasShadow: YES];
1271   [win setReleasedWhenClosed: NO];
1272   [win setDelegate: self];
1273   [[win contentView] addSubview: textField];
1274 /*  [win setBackgroundColor: col]; */
1275   [win setOpaque: NO];
1277   return self;
1280 - (void) dealloc
1282   [win close];
1283   [win release];
1284   [textField release];
1285   [super dealloc];
1288 - (void) setText: (char *)text
1290   NSString *str = [NSString stringWithUTF8String: text];
1291   NSRect r  = [textField frame];
1292   NSSize tooltipDims;
1294   [textField setStringValue: str];
1295   tooltipDims = [[textField cell] cellSize];
1297   r.size.width = tooltipDims.width;
1298   r.size.height = tooltipDims.height;
1299   [textField setFrame: r];
1302 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1304   NSRect wr = [win frame];
1306   wr.origin = NSMakePoint (x, y);
1307   wr.size = [textField frame].size;
1309   [win setFrame: wr display: YES];
1310   [win orderFront: self];
1311   [win display];
1312   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1313                                          selector: @selector (hide)
1314                                          userInfo: nil repeats: NO];
1315   [timer retain];
1318 - (void) hide
1320   [win close];
1321   if (timer != nil)
1322     {
1323       if ([timer isValid])
1324         [timer invalidate];
1325       [timer release];
1326       timer = nil;
1327     }
1330 - (BOOL) isActive
1332   return timer != nil;
1335 - (NSRect) frame
1337   return [textField frame];
1340 @end  /* EmacsTooltip */
1344 /* ==========================================================================
1346     Popup Dialog: implementing functions
1348    ========================================================================== */
1351 static Lisp_Object
1352 pop_down_menu (Lisp_Object arg)
1354   struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1355   if (popup_activated_flag)
1356     {
1357       popup_activated_flag = 0;
1358       BLOCK_INPUT;
1359       [NSApp endModalSession: popupSession];
1360       [((EmacsDialogPanel *) (p->pointer)) close];
1361       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1362       UNBLOCK_INPUT;
1363     }
1364   return Qnil;
1368 Lisp_Object
1369 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1371   id dialog;
1372   Lisp_Object window, tem, title;
1373   struct frame *f;
1374   NSPoint p;
1375   BOOL isQ;
1377   NSTRACE (x-popup-dialog);
1379   check_ns ();
1381   isQ = NILP (header);
1383   if (EQ (position, Qt)
1384       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1385                                || EQ (XCAR (position), Qtool_bar))))
1386     {
1387       window = selected_window;
1388     }
1389   else if (CONSP (position))
1390     {
1391       Lisp_Object tem;
1392       tem = Fcar (position);
1393       if (XTYPE (tem) == Lisp_Cons)
1394         window = Fcar (Fcdr (position));
1395       else
1396         {
1397           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1398           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1399         }
1400     }
1401   else if (WINDOWP (position) || FRAMEP (position))
1402     {
1403       window = position;
1404     }
1405   else
1406     window = Qnil;
1408   if (FRAMEP (window))
1409     f = XFRAME (window);
1410   else if (WINDOWP (window))
1411     {
1412       CHECK_LIVE_WINDOW (window);
1413       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1414     }
1415   else
1416     CHECK_WINDOW (window);
1418   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1419   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1421   title = Fcar (contents);
1422   CHECK_STRING (title);
1424   if (NILP (Fcar (Fcdr (contents))))
1425     /* No buttons specified, add an "Ok" button so users can pop down
1426        the dialog.  */
1427     contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1429   BLOCK_INPUT;
1430   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1431                                            isQuestion: isQ];
1432   {
1433     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1434     record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1435     popup_activated_flag = 1;
1436     tem = [dialog runDialogAt: p];
1437     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1438   }
1439   UNBLOCK_INPUT;
1441   return tem;
1445 /* ==========================================================================
1447     Popup Dialog: class implementation
1449    ========================================================================== */
1451 @interface FlippedView : NSView
1454 @end
1456 @implementation FlippedView
1457 - (BOOL)isFlipped
1459   return YES;
1461 @end
1463 @implementation EmacsDialogPanel
1465 #define SPACER          8.0
1466 #define ICONSIZE        64.0
1467 #define TEXTHEIGHT      20.0
1468 #define MINCELLWIDTH    90.0
1470 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1471               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1473   NSSize spacing = {SPACER, SPACER};
1474   NSRect area;
1475   char this_cmd_name[80];
1476   id cell;
1477   static NSImageView *imgView;
1478   static FlippedView *contentView;
1480   if (imgView == nil)
1481     {
1482       NSImage *img;
1483       area.origin.x   = 3*SPACER;
1484       area.origin.y   = 2*SPACER;
1485       area.size.width = ICONSIZE;
1486       area.size.height= ICONSIZE;
1487       img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1488       [img setScalesWhenResized: YES];
1489       [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1490       imgView = [[NSImageView alloc] initWithFrame: area];
1491       [imgView setImage: img];
1492       [imgView setEditable: NO];
1493       [img release];
1494     }
1496   aStyle = NSTitledWindowMask;
1497   flag = YES;
1498   rows = 0;
1499   cols = 1;
1500   [super initWithContentRect: contentRect styleMask: aStyle
1501                      backing: backingType defer: flag];
1502   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1503   [self setContentView: contentView];
1505   [[self contentView] setAutoresizesSubviews: YES];
1507   [[self contentView] addSubview: imgView];
1508   [self setTitle: @""];
1510   area.origin.x   += ICONSIZE+2*SPACER;
1511 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1512   area.size.width = 400;
1513   area.size.height= TEXTHEIGHT;
1514   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1515   [[self contentView] addSubview: command];
1516   [command setStringValue: ns_app_name];
1517   [command setDrawsBackground: NO];
1518   [command setBezeled: NO];
1519   [command setSelectable: NO];
1520   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1522 /*  area.origin.x   = ICONSIZE+2*SPACER;
1523   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1524   area.size.width = 400;
1525   area.size.height= 2;
1526   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1527   [[self contentView] addSubview: tem];
1528   [tem setTitlePosition: NSNoTitle];
1529   [tem setAutoresizingMask: NSViewWidthSizable];*/
1531 /*  area.origin.x = ICONSIZE+2*SPACER; */
1532   area.origin.y += TEXTHEIGHT+SPACER;
1533   area.size.width = 400;
1534   area.size.height= TEXTHEIGHT;
1535   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1536   [[self contentView] addSubview: title];
1537   [title setDrawsBackground: NO];
1538   [title setBezeled: NO];
1539   [title setSelectable: NO];
1540   [title setFont: [NSFont systemFontOfSize: 11.0]];
1542   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1543   [cell setBordered: NO];
1544   [cell setEnabled: NO];
1545   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1546   [cell setBezelStyle: NSRoundedBezelStyle];
1548   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1549                                       mode: NSHighlightModeMatrix
1550                                  prototype: cell
1551                               numberOfRows: 0
1552                            numberOfColumns: 1];
1553   [[self contentView] addSubview: matrix];
1554   [matrix release];
1555   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1556                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1557   [matrix setIntercellSpacing: spacing];
1559   [self setOneShot: YES];
1560   [self setReleasedWhenClosed: YES];
1561   [self setHidesOnDeactivate: YES];
1562   return self;
1566 - (BOOL)windowShouldClose: (id)sender
1568   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1569   return NO;
1573 void process_dialog (id window, Lisp_Object list)
1575   Lisp_Object item;
1576   int row = 0;
1578   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1579     {
1580       item = XCAR (list);
1581       if (XTYPE (item) == Lisp_String)
1582         {
1583           [window addString: SDATA (item) row: row++];
1584         }
1585       else if (XTYPE (item) == Lisp_Cons)
1586         {
1587           [window addButton: SDATA (XCAR (item))
1588                       value: XCDR (item) row: row++];
1589         }
1590       else if (NILP (item))
1591         {
1592           [window addSplit];
1593           row = 0;
1594         }
1595     }
1599 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1601   id cell;
1603   if (row >= rows)
1604     {
1605       [matrix addRow];
1606       rows++;
1607     }
1608   cell = [matrix cellAtRow: row column: cols-1];
1609   [cell setTarget: self];
1610   [cell setAction: @selector (clicked: )];
1611   [cell setTitle: [NSString stringWithUTF8String: str]];
1612   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1613   [cell setBordered: YES];
1614   [cell setEnabled: YES];
1616   return self;
1620 - addString: (char *)str row: (int)row
1622   id cell;
1624   if (row >= rows)
1625     {
1626       [matrix addRow];
1627       rows++;
1628     }
1629   cell = [matrix cellAtRow: row column: cols-1];
1630   [cell setTitle: [NSString stringWithUTF8String: str]];
1631   [cell setBordered: YES];
1632   [cell setEnabled: NO];
1634   return self;
1638 - addSplit
1640   [matrix addColumn];
1641   cols++;
1642   return self;
1646 - clicked: sender
1648   NSArray *sellist = nil;
1649   EMACS_INT seltag;
1651   sellist = [sender selectedCells];
1652   if ([sellist count]<1)
1653     return self;
1655   seltag = [[sellist objectAtIndex: 0] tag];
1656   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1657     [NSApp stopModalWithCode: seltag];
1658   return self;
1662 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1664   Lisp_Object head;
1665   [super init];
1667   if (XTYPE (contents) == Lisp_Cons)
1668     {
1669       head = Fcar (contents);
1670       process_dialog (self, Fcdr (contents));
1671     }
1672   else
1673     head = contents;
1675   if (XTYPE (head) == Lisp_String)
1676       [title setStringValue:
1677                  [NSString stringWithUTF8String: SDATA (head)]];
1678   else if (isQ == YES)
1679       [title setStringValue: @"Question"];
1680   else
1681       [title setStringValue: @"Information"];
1683   {
1684     int i;
1685     NSRect r, s, t;
1687     if (cols == 1 && rows > 1)  /* Never told where to split */
1688       {
1689         [matrix addColumn];
1690         for (i = 0; i<rows/2; i++)
1691           {
1692             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1693                       atRow: i column: 1];
1694             [matrix removeRow: (rows+1)/2];
1695           }
1696       }
1698     [matrix sizeToFit];
1699     {
1700       NSSize csize = [matrix cellSize];
1701       if (csize.width < MINCELLWIDTH)
1702         {
1703           csize.width = MINCELLWIDTH;
1704           [matrix setCellSize: csize];
1705           [matrix sizeToCells];
1706         }
1707     }
1709     [title sizeToFit];
1710     [command sizeToFit];
1712     t = [matrix frame];
1713     r = [title frame];
1714     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1715       {
1716         t.origin.x   = r.origin.x;
1717         t.size.width = r.size.width;
1718       }
1719     r = [command frame];
1720     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1721       {
1722         t.origin.x   = r.origin.x;
1723         t.size.width = r.size.width;
1724       }
1726     r = [self frame];
1727     s = [(NSView *)[self contentView] frame];
1728     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1729     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1730     [self setFrame: r display: NO];
1731   }
1733   return self;
1737 - (void)dealloc
1739   { [super dealloc]; return; };
1743 - (Lisp_Object)runDialogAt: (NSPoint)p
1745   NSInteger ret;
1747   /* initiate a session that will be ended by pop_down_menu */
1748   popupSession = [NSApp beginModalSessionForWindow: self];
1749   while (popup_activated_flag
1750          && (ret = [NSApp runModalSession: popupSession])
1751               == NSRunContinuesResponse)
1752     {
1753       /* Run this for timers.el, indep of atimers; might not return.
1754          TODO: use return value to avoid calling every iteration. */
1755       timer_check ();
1756       [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1757     }
1759   {                             /* FIXME: BIG UGLY HACK!!! */
1760       Lisp_Object tmp;
1761       *(EMACS_INT*)(&tmp) = ret;
1762       return tmp;
1763   }
1766 @end
1769 /* ==========================================================================
1771     Lisp definitions
1773    ========================================================================== */
1775 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1776        doc: /* Cause the NS menu to be re-calculated.  */)
1777      (void)
1779   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1780   return Qnil;
1784 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1785        doc: /* Pop up a dialog box and return user's selection.
1786 POSITION specifies which frame to use.
1787 This is normally a mouse button event or a window or frame.
1788 If POSITION is t, it means to use the frame the mouse is on.
1789 The dialog box appears in the middle of the specified frame.
1791 CONTENTS specifies the alternatives to display in the dialog box.
1792 It is a list of the form (DIALOG ITEM1 ITEM2...).
1793 Each ITEM is a cons cell (STRING . VALUE).
1794 The return value is VALUE from the chosen item.
1796 An ITEM may also be just a string--that makes a nonselectable item.
1797 An ITEM may also be nil--that means to put all preceding items
1798 on the left of the dialog box and all following items on the right.
1799 \(By default, approximately half appear on each side.)
1801 If HEADER is non-nil, the frame title for the box is "Information",
1802 otherwise it is "Question".
1804 If the user gets rid of the dialog box without making a valid choice,
1805 for instance using the window manager, then this produces a quit and
1806 `x-popup-dialog' does not return.  */)
1807      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1809   return ns_popup_dialog (position, contents, header);
1812 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1813        doc: /* Return t if a menu or popup dialog is active.  */)
1814      (void)
1816   return popup_activated () ? Qt : Qnil;
1819 /* ==========================================================================
1821     Lisp interface declaration
1823    ========================================================================== */
1825 void
1826 syms_of_nsmenu (void)
1828 #ifndef NS_IMPL_COCOA
1829   /* Don't know how to keep track of this in Next/Open/Gnustep.  Always
1830      update menus there.  */
1831   trackingMenu = 1;
1832 #endif
1833   defsubr (&Sx_popup_dialog);
1834   defsubr (&Sns_reset_menu);
1835   defsubr (&Smenu_or_popup_active_p);
1837   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1838   staticpro (&Qdebug_on_next_call);