Use "ASET (a, i, v)" rather than "AREF (a, i) = v".
[emacs.git] / src / nsmenu.m
blobdc9b80a3adce34812a352e091f13bc28ad8a854b
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         = alloca (previous_menu_items_used * sizeof *previous_items);
195       /* lisp preliminaries */
196       buffer = WVAR (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_addr (FVAR (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 = FVAR (f, menu_bar_vector);
232       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
233       submenu_start = alloca (ASIZE (items) * sizeof *submenu_start);
234       submenu_end = alloca (ASIZE (items) * sizeof *submenu_end);
235       submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes);
236       submenu_top_level_items = alloca (ASIZE (items)
237                                         * sizeof *submenu_top_level_items);
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, SSDATA (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 (SSDATA (previous_items[i]),
322                                 SSDATA (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       FVAR (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, SSDATA (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 (memcmp (previous_strings[i], SDATA (string),
432                           min (10, SBYTES (string) + 1)))
433                 break;
434             }
436           if (i == n)
437             {
438               free_menubar_widget_value_tree (first_wv);
439               [pool release];
440               UNBLOCK_INPUT;
441               return;
442             }
443         }
445       [menu clear];
446       for (i = 0; i < ASIZE (items); i += 4)
447         {
448           string = AREF (items, i + 1);
449           if (NILP (string))
450             break;
452           if (n < 100)
453             memcpy (previous_strings[i/4], SDATA (string),
454                     min (10, SBYTES (string) + 1));
456           wv = xmalloc_widget_value ();
457           wv->name = SSDATA (string);
458           wv->value = 0;
459           wv->enabled = 1;
460           wv->button_type = BUTTON_TYPE_NONE;
461           wv->help = Qnil;
462           wv->call_data = (void *) (intptr_t) (-1);
464 #ifdef NS_IMPL_COCOA
465           /* we'll update the real copy under app menu when time comes */
466           if (!strcmp ("Services", wv->name))
467             {
468               /* but we need to make sure it will update on demand */
469               [svcsMenu setFrame: f];
470             }
471           else
472 #endif
473           [menu addSubmenuWithTitle: wv->name forFrame: f];
475           if (prev_wv)
476             prev_wv->next = wv;
477           else
478             first_wv->contents = wv;
479           prev_wv = wv;
480         }
482       last_f = f;
483       if (n < 100)
484         n_previous_strings = n;
485       else
486         n_previous_strings = 0;
488     }
489   free_menubar_widget_value_tree (first_wv);
492 #if NSMENUPROFILE
493   ftime (&tb);
494   t += 1000*tb.time+tb.millitm;
495   fprintf (stderr, "Menu update took %ld msec.\n", t);
496 #endif
498   /* set main menu */
499   if (needsSet)
500     [NSApp setMainMenu: menu];
502   [pool release];
503   UNBLOCK_INPUT;
508 /* Main emacs core entry point for menubar menus: called to indicate that the
509    frame's menus have changed, and the *step representation should be updated
510    from Lisp. */
511 void
512 set_frame_menubar (struct frame *f, int first_time, int deep_p)
514   ns_update_menubar (f, deep_p, nil);
518 /* ==========================================================================
520     Menu: class implementation
522    ========================================================================== */
525 /* Menu that can define itself from Emacs "widget_value"s and will lazily
526    update itself when user clicked.  Based on Carbon/AppKit implementation
527    by Yamamoto Mitsuharu. */
528 @implementation EmacsMenu
530 /* override designated initializer */
531 - initWithTitle: (NSString *)title
533   if ((self = [super initWithTitle: title]))
534     [self setAutoenablesItems: NO];
535   return self;
539 /* used for top-level */
540 - initWithTitle: (NSString *)title frame: (struct frame *)f
542   [self initWithTitle: title];
543   frame = f;
544 #ifdef NS_IMPL_COCOA
545   [self setDelegate: self];
546 #endif
547   return self;
551 - (void)setFrame: (struct frame *)f
553   frame = f;
556 #ifdef NS_IMPL_COCOA
557 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
558 extern NSString *NSMenuDidBeginTrackingNotification;
559 #endif
560 #endif
562 #ifdef NS_IMPL_COCOA
563 -(void)trackingNotification:(NSNotification *)notification
565   /* Update menu in menuNeedsUpdate only while tracking menus.  */
566   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
567                   ? 1 : 0);
569 #endif
571 /* delegate method called when a submenu is being opened: run a 'deep' call
572    to set_frame_menubar */
573 - (void)menuNeedsUpdate: (NSMenu *)menu
575   if (!FRAME_LIVE_P (frame))
576     return;
578   /* Cocoa/Carbon will request update on every keystroke
579      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
580      since key equivalents are handled through emacs.
581      On Leopard, even keystroke events generate SystemDefined event.
582      Third-party applications that enhance mouse / trackpad
583      interaction, or also VNC/Remote Desktop will send events
584      of type AppDefined rather than SysDefined.
585      Menus will fail to show up if they haven't been initialized.
586      AppDefined events may lack timing data.
588      Thus, we rely on the didBeginTrackingNotification notification
589      as above to indicate the need for updates.
590      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
591      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
592   */
593   if (trackingMenu == 0
594       /* Also, don't try this if from an event picked up asynchronously,
595          as lots of lisp evaluation happens in ns_update_menubar. */
596       || handling_signal != 0)
597     return;
598 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
599   ns_update_menubar (frame, 1, self);
603 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
605   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
606       && FRAME_NS_VIEW (SELECTED_FRAME ()))
607     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
608   return YES;
612 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
613    into an accelerator string.  We are only able to display a single character
614    for an accelerator, together with an optional modifier combination.  (Under
615    Carbon more control was possible, but in Cocoa multi-char strings passed to
616    NSMenuItem get ignored.  For now we try to display a super-single letter
617    combo, and return the others as strings to be appended to the item title.
618    (This is signaled by setting keyEquivModMask to 0 for now.) */
619 -(NSString *)parseKeyEquiv: (const char *)key
621   const char *tpos = key;
622   keyEquivModMask = NSCommandKeyMask;
624   if (!key || !strlen (key))
625     return @"";
627   while (*tpos == ' ' || *tpos == '(')
628     tpos++;
629   if ((*tpos == 's') && (*(tpos+1) == '-'))
630     {
631       return [NSString stringWithFormat: @"%c", tpos[2]];
632     }
633   keyEquivModMask = 0; /* signal */
634   return [NSString stringWithUTF8String: tpos];
638 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
640   NSMenuItem *item;
641   widget_value *wv = (widget_value *)wvptr;
643   if (menu_separator_name_p (wv->name))
644     {
645       item = [NSMenuItem separatorItem];
646       [self addItem: item];
647     }
648   else
649     {
650       NSString *title, *keyEq;
651       title = [NSString stringWithUTF8String: wv->name];
652       if (title == nil)
653         title = @"< ? >";  /* (get out in the open so we know about it) */
655       keyEq = [self parseKeyEquiv: wv->key];
656 #ifdef NS_IMPL_COCOA
657       /* OS X just ignores modifier strings longer than one character */
658       if (keyEquivModMask == 0)
659         title = [title stringByAppendingFormat: @" (%@)", keyEq];
660 #endif
662       item = [self addItemWithTitle: (NSString *)title
663                              action: @selector (menuDown:)
664                       keyEquivalent: keyEq];
665       [item setKeyEquivalentModifierMask: keyEquivModMask];
667       [item setEnabled: wv->enabled];
669       /* Draw radio buttons and tickboxes */
670       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
671                            wv->button_type == BUTTON_TYPE_RADIO))
672         [item setState: NSOnState];
673       else
674         [item setState: NSOffState];
676       [item setTag: (NSInteger)wv->call_data];
677     }
679   return item;
683 /* convenience */
684 -(void)clear
686   int n;
688   for (n = [self numberOfItems]-1; n >= 0; n--)
689     {
690       NSMenuItem *item = [self itemAtIndex: n];
691       NSString *title = [item title];
692       if (([title length] == 0  /* OSX 10.5 */
693            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
694            || [@"Apple" isEqualToString: title]) /* older */
695           && ![item isSeparatorItem])
696         continue;
697       [self removeItemAtIndex: n];
698     }
702 - (void)fillWithWidgetValue: (void *)wvptr
704   widget_value *wv = (widget_value *)wvptr;
706   /* clear existing contents */
707   [self setMenuChangedMessagesEnabled: NO];
708   [self clear];
710   /* add new contents */
711   for (; wv != NULL; wv = wv->next)
712     {
713       NSMenuItem *item = [self addItemWithWidgetValue: wv];
715       if (wv->contents)
716         {
717           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
719           [self setSubmenu: submenu forItem: item];
720           [submenu fillWithWidgetValue: wv->contents];
721           [submenu release];
722           [item setAction: nil];
723         }
724     }
726   [self setMenuChangedMessagesEnabled: YES];
727 #ifdef NS_IMPL_GNUSTEP
728   if ([[self window] isVisible])
729     [self sizeToFit];
730 #else
731 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_2
732   if ([self supermenu] == nil)
733     [self sizeToFit];
734 #endif
735 #endif
739 /* adds an empty submenu and returns it */
740 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
742   NSString *titleStr = [NSString stringWithUTF8String: title];
743   NSMenuItem *item = [self addItemWithTitle: titleStr
744                                      action: nil /*@selector (menuDown:) */
745                               keyEquivalent: @""];
746   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
747   [self setSubmenu: submenu forItem: item];
748   [submenu release];
749   return submenu;
752 /* run a menu in popup mode */
753 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
754                  keymaps: (int)keymaps
756   EmacsView *view = FRAME_NS_VIEW (f);
757   NSEvent *e, *event;
758   long retVal;
760 /*   p = [view convertPoint:p fromView: nil]; */
761   p.y = NSHeight ([view frame]) - p.y;
762   e = [[view window] currentEvent];
763    event = [NSEvent mouseEventWithType: NSRightMouseDown
764                               location: p
765                          modifierFlags: 0
766                              timestamp: [e timestamp]
767                           windowNumber: [[view window] windowNumber]
768                                context: [e context]
769                            eventNumber: 0/*[e eventNumber] */
770                             clickCount: 1
771                               pressure: 0];
773   context_menu_value = -1;
774   [NSMenu popUpContextMenu: self withEvent: event forView: view];
775   retVal = context_menu_value;
776   context_menu_value = 0;
777   return retVal > 0
778       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
779       : Qnil;
782 @end  /* EmacsMenu */
786 /* ==========================================================================
788     Context Menu: implementing functions
790    ========================================================================== */
792 Lisp_Object
793 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
794               Lisp_Object title, const char **error)
796   EmacsMenu *pmenu;
797   NSPoint p;
798   Lisp_Object tem;
799   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
800   widget_value *wv, *first_wv = 0;
802   p.x = x; p.y = y;
804   /* now parse stage 2 as in ns_update_menubar */
805   wv = xmalloc_widget_value ();
806   wv->name = "contextmenu";
807   wv->value = 0;
808   wv->enabled = 1;
809   wv->button_type = BUTTON_TYPE_NONE;
810   wv->help = Qnil;
811   first_wv = wv;
813 #if 0
814   /* FIXME: a couple of one-line differences prevent reuse */
815   wv = digest_single_submenu (0, menu_items_used, Qnil);
816 #else
817   {
818   widget_value *save_wv = 0, *prev_wv = 0;
819   widget_value **submenu_stack
820     = alloca (menu_items_used * sizeof *submenu_stack);
821 /*   Lisp_Object *subprefix_stack
822        = alloca (menu_items_used * sizeof *subprefix_stack); */
823   int submenu_depth = 0;
824   int first_pane = 1;
825   int i;
827   /* Loop over all panes and items, filling in the tree.  */
828   i = 0;
829   while (i < menu_items_used)
830     {
831       if (EQ (AREF (menu_items, i), Qnil))
832         {
833           submenu_stack[submenu_depth++] = save_wv;
834           save_wv = prev_wv;
835           prev_wv = 0;
836           first_pane = 1;
837           i++;
838         }
839       else if (EQ (AREF (menu_items, i), Qlambda))
840         {
841           prev_wv = save_wv;
842           save_wv = submenu_stack[--submenu_depth];
843           first_pane = 0;
844           i++;
845         }
846       else if (EQ (AREF (menu_items, i), Qt)
847                && submenu_depth != 0)
848         i += MENU_ITEMS_PANE_LENGTH;
849       /* Ignore a nil in the item list.
850          It's meaningful only for dialog boxes.  */
851       else if (EQ (AREF (menu_items, i), Qquote))
852         i += 1;
853       else if (EQ (AREF (menu_items, i), Qt))
854         {
855           /* Create a new pane.  */
856           Lisp_Object pane_name, prefix;
857           const char *pane_string;
859           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
860           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
862 #ifndef HAVE_MULTILINGUAL_MENU
863           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
864             {
865               pane_name = ENCODE_MENU_STRING (pane_name);
866               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
867             }
868 #endif
869           pane_string = (NILP (pane_name)
870                          ? "" : SSDATA (pane_name));
871           /* If there is just one top-level pane, put all its items directly
872              under the top-level menu.  */
873           if (menu_items_n_panes == 1)
874             pane_string = "";
876           /* If the pane has a meaningful name,
877              make the pane a top-level menu item
878              with its items as a submenu beneath it.  */
879           if (!keymaps && strcmp (pane_string, ""))
880             {
881               wv = xmalloc_widget_value ();
882               if (save_wv)
883                 save_wv->next = wv;
884               else
885                 first_wv->contents = wv;
886               wv->name = pane_string;
887               if (keymaps && !NILP (prefix))
888                 wv->name++;
889               wv->value = 0;
890               wv->enabled = 1;
891               wv->button_type = BUTTON_TYPE_NONE;
892               wv->help = Qnil;
893               save_wv = wv;
894               prev_wv = 0;
895             }
896           else if (first_pane)
897             {
898               save_wv = wv;
899               prev_wv = 0;
900             }
901           first_pane = 0;
902           i += MENU_ITEMS_PANE_LENGTH;
903         }
904       else
905         {
906           /* Create a new item within current pane.  */
907           Lisp_Object item_name, enable, descrip, def, type, selected, help;
908           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
909           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
910           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
911           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
912           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
913           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
914           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
916 #ifndef HAVE_MULTILINGUAL_MENU
917           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
918             {
919               item_name = ENCODE_MENU_STRING (item_name);
920               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
921             }
923           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
924             {
925               descrip = ENCODE_MENU_STRING (descrip);
926               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
927             }
928 #endif /* not HAVE_MULTILINGUAL_MENU */
930           wv = xmalloc_widget_value ();
931           if (prev_wv)
932             prev_wv->next = wv;
933           else
934             save_wv->contents = wv;
935           wv->name = SSDATA (item_name);
936           if (!NILP (descrip))
937             wv->key = SSDATA (descrip);
938           wv->value = 0;
939           /* If this item has a null value,
940              make the call_data null so that it won't display a box
941              when the mouse is on it.  */
942           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
943           wv->enabled = !NILP (enable);
945           if (NILP (type))
946             wv->button_type = BUTTON_TYPE_NONE;
947           else if (EQ (type, QCtoggle))
948             wv->button_type = BUTTON_TYPE_TOGGLE;
949           else if (EQ (type, QCradio))
950             wv->button_type = BUTTON_TYPE_RADIO;
951           else
952             abort ();
954           wv->selected = !NILP (selected);
956           if (! STRINGP (help))
957             help = Qnil;
959           wv->help = help;
961           prev_wv = wv;
963           i += MENU_ITEMS_ITEM_LENGTH;
964         }
965     }
966   }
967 #endif
969   if (!NILP (title))
970     {
971       widget_value *wv_title = xmalloc_widget_value ();
972       widget_value *wv_sep = xmalloc_widget_value ();
974       /* Maybe replace this separator with a bitmap or owner-draw item
975          so that it looks better.  Having two separators looks odd.  */
976       wv_sep->name = "--";
977       wv_sep->next = first_wv->contents;
978       wv_sep->help = Qnil;
980 #ifndef HAVE_MULTILINGUAL_MENU
981       if (STRING_MULTIBYTE (title))
982         title = ENCODE_MENU_STRING (title);
983 #endif
985       wv_title->name = SSDATA (title);
986       wv_title->enabled = NO;
987       wv_title->button_type = BUTTON_TYPE_NONE;
988       wv_title->help = Qnil;
989       wv_title->next = wv_sep;
990       first_wv->contents = wv_title;
991     }
993   pmenu = [[EmacsMenu alloc] initWithTitle:
994                                [NSString stringWithUTF8String: SSDATA (title)]];
995   [pmenu fillWithWidgetValue: first_wv->contents];
996   free_menubar_widget_value_tree (first_wv);
997   unbind_to (specpdl_count, Qnil);
999   popup_activated_flag = 1;
1000   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1001   popup_activated_flag = 0;
1002   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1004   return tem;
1008 /* ==========================================================================
1010     Toolbar: externally-called functions
1012    ========================================================================== */
1014 void
1015 free_frame_tool_bar (FRAME_PTR f)
1016 /* --------------------------------------------------------------------------
1017     Under NS we just hide the toolbar until it might be needed again.
1018    -------------------------------------------------------------------------- */
1020   BLOCK_INPUT;
1021   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1022   FRAME_TOOLBAR_HEIGHT (f) = 0;
1023   UNBLOCK_INPUT;
1026 void
1027 update_frame_tool_bar (FRAME_PTR f)
1028 /* --------------------------------------------------------------------------
1029     Update toolbar contents
1030    -------------------------------------------------------------------------- */
1032   int i;
1033   EmacsView *view = FRAME_NS_VIEW (f);
1034   NSWindow *window = [view window];
1035   EmacsToolbar *toolbar = [view toolbar];
1037   BLOCK_INPUT;
1038   [toolbar clearActive];
1040   /* update EmacsToolbar as in GtkUtils, build items list */
1041   for (i = 0; i < f->n_tool_bar_items; ++i)
1042     {
1043 #define TOOLPROP(IDX) AREF (FVAR (f, tool_bar_items), \
1044                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1046       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_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           eassert (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       id 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];
1370       [panel close];
1371       [unwind_data->pool release];
1372       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1373     }
1375   xfree (unwind_data);
1376   UNBLOCK_INPUT;
1378   return Qnil;
1382 Lisp_Object
1383 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1385   id dialog;
1386   Lisp_Object window, tem, title;
1387   struct frame *f;
1388   NSPoint p;
1389   BOOL isQ;
1390   NSAutoreleasePool *pool;
1392   NSTRACE (x-popup-dialog);
1394   check_ns ();
1396   isQ = NILP (header);
1398   if (EQ (position, Qt)
1399       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1400                                || EQ (XCAR (position), Qtool_bar))))
1401     {
1402       window = selected_window;
1403     }
1404   else if (CONSP (position))
1405     {
1406       Lisp_Object tem;
1407       tem = Fcar (position);
1408       if (XTYPE (tem) == Lisp_Cons)
1409         window = Fcar (Fcdr (position));
1410       else
1411         {
1412           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1413           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1414         }
1415     }
1416   else if (WINDOWP (position) || FRAMEP (position))
1417     {
1418       window = position;
1419     }
1420   else
1421     window = Qnil;
1423   if (FRAMEP (window))
1424     f = XFRAME (window);
1425   else if (WINDOWP (window))
1426     {
1427       CHECK_LIVE_WINDOW (window);
1428       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1429     }
1430   else
1431     CHECK_WINDOW (window);
1433   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1434   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1436   title = Fcar (contents);
1437   CHECK_STRING (title);
1439   if (NILP (Fcar (Fcdr (contents))))
1440     /* No buttons specified, add an "Ok" button so users can pop down
1441        the dialog.  */
1442     contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1444   BLOCK_INPUT;
1445   pool = [[NSAutoreleasePool alloc] init];
1446   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1447                                            isQuestion: isQ];
1449   {
1450     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1451     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1453     unwind_data->pool = pool;
1454     unwind_data->dialog = dialog;
1456     record_unwind_protect (pop_down_menu, make_save_value (unwind_data, 0));
1457     popup_activated_flag = 1;
1458     tem = [dialog runDialogAt: p];
1459     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1460   }
1462   UNBLOCK_INPUT;
1464   return tem;
1468 /* ==========================================================================
1470     Popup Dialog: class implementation
1472    ========================================================================== */
1474 @interface FlippedView : NSView
1477 @end
1479 @implementation FlippedView
1480 - (BOOL)isFlipped
1482   return YES;
1484 @end
1486 @implementation EmacsDialogPanel
1488 #define SPACER          8.0
1489 #define ICONSIZE        64.0
1490 #define TEXTHEIGHT      20.0
1491 #define MINCELLWIDTH    90.0
1493 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1494               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1496   NSSize spacing = {SPACER, SPACER};
1497   NSRect area;
1498   id cell;
1499   NSImageView *imgView;
1500   FlippedView *contentView;
1501   NSImage *img;
1503   area.origin.x   = 3*SPACER;
1504   area.origin.y   = 2*SPACER;
1505   area.size.width = ICONSIZE;
1506   area.size.height= ICONSIZE;
1507   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1508   [img setScalesWhenResized: YES];
1509   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1510   imgView = [[NSImageView alloc] initWithFrame: area];
1511   [imgView setImage: img];
1512   [imgView setEditable: NO];
1513   [img autorelease];
1514   [imgView autorelease];
1516   aStyle = NSTitledWindowMask;
1517   flag = YES;
1518   rows = 0;
1519   cols = 1;
1520   [super initWithContentRect: contentRect styleMask: aStyle
1521                      backing: backingType defer: flag];
1522   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1523   [contentView autorelease];
1525   [self setContentView: contentView];
1527   [[self contentView] setAutoresizesSubviews: YES];
1529   [[self contentView] addSubview: imgView];
1530   [self setTitle: @""];
1532   area.origin.x   += ICONSIZE+2*SPACER;
1533 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1534   area.size.width = 400;
1535   area.size.height= TEXTHEIGHT;
1536   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1537   [[self contentView] addSubview: command];
1538   [command setStringValue: ns_app_name];
1539   [command setDrawsBackground: NO];
1540   [command setBezeled: NO];
1541   [command setSelectable: NO];
1542   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1544 /*  area.origin.x   = ICONSIZE+2*SPACER;
1545   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1546   area.size.width = 400;
1547   area.size.height= 2;
1548   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1549   [[self contentView] addSubview: tem];
1550   [tem setTitlePosition: NSNoTitle];
1551   [tem setAutoresizingMask: NSViewWidthSizable];*/
1553 /*  area.origin.x = ICONSIZE+2*SPACER; */
1554   area.origin.y += TEXTHEIGHT+SPACER;
1555   area.size.width = 400;
1556   area.size.height= TEXTHEIGHT;
1557   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1558   [[self contentView] addSubview: title];
1559   [title setDrawsBackground: NO];
1560   [title setBezeled: NO];
1561   [title setSelectable: NO];
1562   [title setFont: [NSFont systemFontOfSize: 11.0]];
1564   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1565   [cell setBordered: NO];
1566   [cell setEnabled: NO];
1567   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1568   [cell setBezelStyle: NSRoundedBezelStyle];
1570   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1571                                       mode: NSHighlightModeMatrix
1572                                  prototype: cell
1573                               numberOfRows: 0
1574                            numberOfColumns: 1];
1575   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1576                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1577   [matrix setIntercellSpacing: spacing];
1578   [matrix autorelease];
1580   [[self contentView] addSubview: matrix];
1581   [self setOneShot: YES];
1582   [self setReleasedWhenClosed: YES];
1583   [self setHidesOnDeactivate: YES];
1584   return self;
1588 - (BOOL)windowShouldClose: (id)sender
1590   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1591   return NO;
1595 void process_dialog (id window, Lisp_Object list)
1597   Lisp_Object item;
1598   int row = 0;
1600   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1601     {
1602       item = XCAR (list);
1603       if (XTYPE (item) == Lisp_String)
1604         {
1605           [window addString: SSDATA (item) row: row++];
1606         }
1607       else if (XTYPE (item) == Lisp_Cons)
1608         {
1609           [window addButton: SSDATA (XCAR (item))
1610                       value: XCDR (item) row: row++];
1611         }
1612       else if (NILP (item))
1613         {
1614           [window addSplit];
1615           row = 0;
1616         }
1617     }
1621 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1623   id cell;
1625   if (row >= rows)
1626     {
1627       [matrix addRow];
1628       rows++;
1629     }
1630   cell = [matrix cellAtRow: row column: cols-1];
1631   [cell setTarget: self];
1632   [cell setAction: @selector (clicked: )];
1633   [cell setTitle: [NSString stringWithUTF8String: str]];
1634   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1635   [cell setBordered: YES];
1636   [cell setEnabled: YES];
1638   return self;
1642 - addString: (char *)str row: (int)row
1644   id cell;
1646   if (row >= rows)
1647     {
1648       [matrix addRow];
1649       rows++;
1650     }
1651   cell = [matrix cellAtRow: row column: cols-1];
1652   [cell setTitle: [NSString stringWithUTF8String: str]];
1653   [cell setBordered: YES];
1654   [cell setEnabled: NO];
1656   return self;
1660 - addSplit
1662   [matrix addColumn];
1663   cols++;
1664   return self;
1668 - clicked: sender
1670   NSArray *sellist = nil;
1671   EMACS_INT seltag;
1673   sellist = [sender selectedCells];
1674   if ([sellist count]<1)
1675     return self;
1677   seltag = [[sellist objectAtIndex: 0] tag];
1678   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1679     [NSApp stopModalWithCode: seltag];
1680   return self;
1684 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1686   Lisp_Object head;
1687   [super init];
1689   if (XTYPE (contents) == Lisp_Cons)
1690     {
1691       head = Fcar (contents);
1692       process_dialog (self, Fcdr (contents));
1693     }
1694   else
1695     head = contents;
1697   if (XTYPE (head) == Lisp_String)
1698       [title setStringValue:
1699                  [NSString stringWithUTF8String: SSDATA (head)]];
1700   else if (isQ == YES)
1701       [title setStringValue: @"Question"];
1702   else
1703       [title setStringValue: @"Information"];
1705   {
1706     int i;
1707     NSRect r, s, t;
1709     if (cols == 1 && rows > 1)  /* Never told where to split */
1710       {
1711         [matrix addColumn];
1712         for (i = 0; i<rows/2; i++)
1713           {
1714             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1715                       atRow: i column: 1];
1716             [matrix removeRow: (rows+1)/2];
1717           }
1718       }
1720     [matrix sizeToFit];
1721     {
1722       NSSize csize = [matrix cellSize];
1723       if (csize.width < MINCELLWIDTH)
1724         {
1725           csize.width = MINCELLWIDTH;
1726           [matrix setCellSize: csize];
1727           [matrix sizeToCells];
1728         }
1729     }
1731     [title sizeToFit];
1732     [command sizeToFit];
1734     t = [matrix frame];
1735     r = [title frame];
1736     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1737       {
1738         t.origin.x   = r.origin.x;
1739         t.size.width = r.size.width;
1740       }
1741     r = [command frame];
1742     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1743       {
1744         t.origin.x   = r.origin.x;
1745         t.size.width = r.size.width;
1746       }
1748     r = [self frame];
1749     s = [(NSView *)[self contentView] frame];
1750     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1751     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1752     [self setFrame: r display: NO];
1753   }
1755   return self;
1759 - (Lisp_Object)runDialogAt: (NSPoint)p
1761   NSInteger ret;
1763   /* initiate a session that will be ended by pop_down_menu */
1764   popupSession = [NSApp beginModalSessionForWindow: self];
1765   while (popup_activated_flag
1766          && (ret = [NSApp runModalSession: popupSession])
1767               == NSRunContinuesResponse)
1768     {
1769       /* Run this for timers.el, indep of atimers; might not return.
1770          TODO: use return value to avoid calling every iteration. */
1771       timer_check ();
1772       [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1773     }
1775   {                             /* FIXME: BIG UGLY HACK!!! */
1776       Lisp_Object tmp;
1777       *(EMACS_INT*)(&tmp) = ret;
1778       return tmp;
1779   }
1782 @end
1785 /* ==========================================================================
1787     Lisp definitions
1789    ========================================================================== */
1791 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1792        doc: /* Cause the NS menu to be re-calculated.  */)
1793      (void)
1795   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1796   return Qnil;
1800 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1801        doc: /* Pop up a dialog box and return user's selection.
1802 POSITION specifies which frame to use.
1803 This is normally a mouse button event or a window or frame.
1804 If POSITION is t, it means to use the frame the mouse is on.
1805 The dialog box appears in the middle of the specified frame.
1807 CONTENTS specifies the alternatives to display in the dialog box.
1808 It is a list of the form (DIALOG ITEM1 ITEM2...).
1809 Each ITEM is a cons cell (STRING . VALUE).
1810 The return value is VALUE from the chosen item.
1812 An ITEM may also be just a string--that makes a nonselectable item.
1813 An ITEM may also be nil--that means to put all preceding items
1814 on the left of the dialog box and all following items on the right.
1815 \(By default, approximately half appear on each side.)
1817 If HEADER is non-nil, the frame title for the box is "Information",
1818 otherwise it is "Question".
1820 If the user gets rid of the dialog box without making a valid choice,
1821 for instance using the window manager, then this produces a quit and
1822 `x-popup-dialog' does not return.  */)
1823      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1825   return ns_popup_dialog (position, contents, header);
1828 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1829        doc: /* Return t if a menu or popup dialog is active.  */)
1830      (void)
1832   return popup_activated () ? Qt : Qnil;
1835 /* ==========================================================================
1837     Lisp interface declaration
1839    ========================================================================== */
1841 void
1842 syms_of_nsmenu (void)
1844 #ifndef NS_IMPL_COCOA
1845   /* Don't know how to keep track of this in Next/Open/Gnustep.  Always
1846      update menus there.  */
1847   trackingMenu = 1;
1848 #endif
1849   defsubr (&Sx_popup_dialog);
1850   defsubr (&Sns_reset_menu);
1851   defsubr (&Smenu_or_popup_active_p);
1853   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1854   staticpro (&Qdebug_on_next_call);