Sync with Tramp 2.2.6.
[emacs.git] / src / nsmenu.m
blobd0f3e45e939dcfac274bc2a5e26ecb4cc92615b4
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;
77 /* Nonzero means we are tracking and updating menus.  */
78 static int trackingMenu;
81 /* NOTE: toolbar implementation is at end,
82   following complete menu implementation. */
85 /* ==========================================================================
87     Menu: Externally-called functions
89    ========================================================================== */
92 /* FIXME: not currently used, but should normalize with other terms. */
93 void
94 x_activate_menubar (struct frame *f)
96     fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
100 /* Supposed to discard menubar and free storage.  Since we share the
101    menubar among frames and update its context for the focused window,
102    there is nothing to do here. */
103 void
104 free_frame_menubar (struct frame *f)
106   return;
111 popup_activated (void)
113   return popup_activated_flag;
117 /* --------------------------------------------------------------------------
118     Update menubar.  Three cases:
119     1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
120        just top-level menu strings (OS X), or goto case (2) (GNUstep).
121     2) deep_p = 1, submenu = nil: Recompute all submenus.
122     3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
123    -------------------------------------------------------------------------- */
124 void
125 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
127   NSAutoreleasePool *pool;
128   id menu = [NSApp mainMenu];
129   static EmacsMenu *last_submenu = nil;
130   BOOL needsSet = NO;
131   const char *submenuTitle = [[submenu title] UTF8String];
132   extern int waiting_for_input;
133   int owfi;
134   Lisp_Object items;
135   widget_value *wv, *first_wv, *prev_wv = 0;
136   int i;
138 #if NSMENUPROFILE
139   struct timeb tb;
140   long t;
141 #endif
143   NSTRACE (set_frame_menubar);
145   if (f != SELECTED_FRAME ())
146       return;
147   XSETFRAME (Vmenu_updating_frame, f);
148 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
150   BLOCK_INPUT;
151   pool = [[NSAutoreleasePool alloc] init];
153   /* Menu may have been created automatically; if so, discard it. */
154   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
155     {
156       [menu release];
157       menu = nil;
158     }
160   if (menu == nil)
161     {
162       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
163       needsSet = YES;
164     }
165   else
166     {  /* close up anything on there */
167       id attMenu = [menu attachedMenu];
168       if (attMenu != nil)
169         [attMenu close];
170     }
172 #if NSMENUPROFILE
173   ftime (&tb);
174   t = -(1000*tb.time+tb.millitm);
175 #endif
177 #ifdef NS_IMPL_GNUSTEP
178   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
179 #endif
181   if (deep_p)
182     {
183       /* Fully parse one or more of the submenus. */
184       int n = 0;
185       int *submenu_start, *submenu_end;
186       int *submenu_top_level_items, *submenu_n_panes;
187       struct buffer *prev = current_buffer;
188       Lisp_Object buffer;
189       ptrdiff_t specpdl_count = SPECPDL_INDEX ();
190       int previous_menu_items_used = f->menu_bar_items_used;
191       Lisp_Object *previous_items
192         = alloca (previous_menu_items_used * sizeof *previous_items);
194       /* lisp preliminaries */
195       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
196       specbind (Qinhibit_quit, Qt);
197       specbind (Qdebug_on_next_call, Qnil);
198       record_unwind_save_match_data ();
199       if (NILP (Voverriding_local_map_menu_flag))
200         {
201           specbind (Qoverriding_terminal_local_map, Qnil);
202           specbind (Qoverriding_local_map, Qnil);
203         }
204       set_buffer_internal_1 (XBUFFER (buffer));
206       /* TODO: for some reason this is not needed in other terms,
207            but some menu updates call Info-extract-pointer which causes
208            abort-on-error if waiting-for-input.  Needs further investigation. */
209       owfi = waiting_for_input;
210       waiting_for_input = 0;
212       /* lucid hook and possible reset */
213       safe_run_hooks (Qactivate_menubar_hook);
214       if (! NILP (Vlucid_menu_bar_dirty_flag))
215         call0 (Qrecompute_lucid_menubar);
216       safe_run_hooks (Qmenu_bar_update_hook);
217       fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
219       /* Now ready to go */
220       items = FRAME_MENU_BAR_ITEMS (f);
222       /* Save the frame's previous menu bar contents data */
223       if (previous_menu_items_used)
224         memcpy (previous_items, aref_addr (f->menu_bar_vector, 0),
225                 previous_menu_items_used * sizeof (Lisp_Object));
227       /* parse stage 1: extract from lisp */
228       save_menu_items ();
230       menu_items = f->menu_bar_vector;
231       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
232       submenu_start = alloca (ASIZE (items) * sizeof *submenu_start);
233       submenu_end = alloca (ASIZE (items) * sizeof *submenu_end);
234       submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes);
235       submenu_top_level_items = alloca (ASIZE (items)
236                                         * sizeof *submenu_top_level_items);
237       init_menu_items ();
238       for (i = 0; i < ASIZE (items); i += 4)
239         {
240           Lisp_Object key, string, maps;
242           key = AREF (items, i);
243           string = AREF (items, i + 1);
244           maps = AREF (items, i + 2);
245           if (NILP (string))
246             break;
248           /* FIXME: we'd like to only parse the needed submenu, but this
249                was causing crashes in the _common parsing code.. need to make
250                sure proper initialization done.. */
251 /*        if (submenu && strcmp (submenuTitle, SSDATA (string)))
252              continue; */
254           submenu_start[i] = menu_items_used;
256           menu_items_n_panes = 0;
257           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
258           submenu_n_panes[i] = menu_items_n_panes;
259           submenu_end[i] = menu_items_used;
260           n++;
261         }
263       finish_menu_items ();
264       waiting_for_input = owfi;
267       if (submenu && n == 0)
268         {
269           /* should have found a menu for this one but didn't */
270           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
271                   submenuTitle);
272           discard_menu_items ();
273           unbind_to (specpdl_count, Qnil);
274           [pool release];
275           UNBLOCK_INPUT;
276           return;
277         }
279       /* parse stage 2: insert into lucid 'widget_value' structures
280          [comments in other terms say not to evaluate lisp code here] */
281       wv = xmalloc_widget_value ();
282       wv->name = "menubar";
283       wv->value = 0;
284       wv->enabled = 1;
285       wv->button_type = BUTTON_TYPE_NONE;
286       wv->help = Qnil;
287       first_wv = wv;
289       for (i = 0; i < 4*n; i += 4)
290         {
291           menu_items_n_panes = submenu_n_panes[i];
292           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
293                                       submenu_top_level_items[i]);
294           if (prev_wv)
295             prev_wv->next = wv;
296           else
297             first_wv->contents = wv;
298           /* Don't set wv->name here; GC during the loop might relocate it.  */
299           wv->enabled = 1;
300           wv->button_type = BUTTON_TYPE_NONE;
301           prev_wv = wv;
302         }
304       set_buffer_internal_1 (prev);
306       /* Compare the new menu items with previous, and leave off if no change */
307       /* FIXME: following other terms here, but seems like this should be
308            done before parse stage 2 above, since its results aren't used */
309       if (previous_menu_items_used
310           && (!submenu || (submenu && submenu == last_submenu))
311           && menu_items_used == previous_menu_items_used)
312         {
313           for (i = 0; i < previous_menu_items_used; i++)
314             /* FIXME: this ALWAYS fails on Buffers menu items.. something
315                  about their strings causes them to change every time, so we
316                  double-check failures */
317             if (!EQ (previous_items[i], AREF (menu_items, i)))
318               if (!(STRINGP (previous_items[i])
319                     && STRINGP (AREF (menu_items, i))
320                     && !strcmp (SSDATA (previous_items[i]),
321                                 SSDATA (AREF (menu_items, i)))))
322                   break;
323           if (i == previous_menu_items_used)
324             {
325               /* No change.. */
327 #if NSMENUPROFILE
328               ftime (&tb);
329               t += 1000*tb.time+tb.millitm;
330               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
331 #endif
333               free_menubar_widget_value_tree (first_wv);
334               discard_menu_items ();
335               unbind_to (specpdl_count, Qnil);
336               [pool release];
337               UNBLOCK_INPUT;
338               return;
339             }
340         }
341       /* The menu items are different, so store them in the frame */
342       /* FIXME: this is not correct for single-submenu case */
343       fset_menu_bar_vector (f, menu_items);
344       f->menu_bar_items_used = menu_items_used;
346       /* Calls restore_menu_items, etc., as they were outside */
347       unbind_to (specpdl_count, Qnil);
349       /* Parse stage 2a: now GC cannot happen during the lifetime of the
350          widget_value, so it's safe to store data from a Lisp_String */
351       wv = first_wv->contents;
352       for (i = 0; i < ASIZE (items); i += 4)
353         {
354           Lisp_Object string;
355           string = AREF (items, i + 1);
356           if (NILP (string))
357             break;
358 /*           if (submenu && strcmp (submenuTitle, SSDATA (string)))
359                continue; */
361           wv->name = SSDATA (string);
362           update_submenu_strings (wv->contents);
363           wv = wv->next;
364         }
366       /* Now, update the NS menu; if we have a submenu, use that, otherwise
367          create a new menu for each sub and fill it. */
368       if (submenu)
369         {
370           for (wv = first_wv->contents; wv; wv = wv->next)
371             {
372               if (!strcmp (submenuTitle, wv->name))
373                 {
374                   [submenu fillWithWidgetValue: wv->contents];
375                   last_submenu = submenu;
376                   break;
377                 }
378             }
379         }
380       else
381         {
382           [menu fillWithWidgetValue: first_wv->contents];
383         }
385     }
386   else
387     {
388       static int n_previous_strings = 0;
389       static char previous_strings[100][10];
390       static struct frame *last_f = NULL;
391       int n;
392       Lisp_Object string;
394       wv = xmalloc_widget_value ();
395       wv->name = "menubar";
396       wv->value = 0;
397       wv->enabled = 1;
398       wv->button_type = BUTTON_TYPE_NONE;
399       wv->help = Qnil;
400       first_wv = wv;
402       /* Make widget-value tree w/ just the top level menu bar strings */
403       items = FRAME_MENU_BAR_ITEMS (f);
404       if (NILP (items))
405         {
406           free_menubar_widget_value_tree (first_wv);
407           [pool release];
408           UNBLOCK_INPUT;
409           return;
410         }
413       /* check if no change.. this mechanism is a bit rough, but ready */
414       n = ASIZE (items) / 4;
415       if (f == last_f && n_previous_strings == n)
416         {
417           for (i = 0; i<n; i++)
418             {
419               string = AREF (items, 4*i+1);
421               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
422                 continue;
423               if (NILP (string))
424                 {
425                   if (previous_strings[i][0])
426                     break;
427                   else
428                     continue;
429                 }
430               else if (memcmp (previous_strings[i], SDATA (string),
431                           min (10, SBYTES (string) + 1)))
432                 break;
433             }
435           if (i == n)
436             {
437               free_menubar_widget_value_tree (first_wv);
438               [pool release];
439               UNBLOCK_INPUT;
440               return;
441             }
442         }
444       [menu clear];
445       for (i = 0; i < ASIZE (items); i += 4)
446         {
447           string = AREF (items, i + 1);
448           if (NILP (string))
449             break;
451           if (n < 100)
452             memcpy (previous_strings[i/4], SDATA (string),
453                     min (10, SBYTES (string) + 1));
455           wv = xmalloc_widget_value ();
456           wv->name = SSDATA (string);
457           wv->value = 0;
458           wv->enabled = 1;
459           wv->button_type = BUTTON_TYPE_NONE;
460           wv->help = Qnil;
461           wv->call_data = (void *) (intptr_t) (-1);
463 #ifdef NS_IMPL_COCOA
464           /* we'll update the real copy under app menu when time comes */
465           if (!strcmp ("Services", wv->name))
466             {
467               /* but we need to make sure it will update on demand */
468               [svcsMenu setFrame: f];
469             }
470           else
471 #endif
472           [menu addSubmenuWithTitle: wv->name forFrame: f];
474           if (prev_wv)
475             prev_wv->next = wv;
476           else
477             first_wv->contents = wv;
478           prev_wv = wv;
479         }
481       last_f = f;
482       if (n < 100)
483         n_previous_strings = n;
484       else
485         n_previous_strings = 0;
487     }
488   free_menubar_widget_value_tree (first_wv);
491 #if NSMENUPROFILE
492   ftime (&tb);
493   t += 1000*tb.time+tb.millitm;
494   fprintf (stderr, "Menu update took %ld msec.\n", t);
495 #endif
497   /* set main menu */
498   if (needsSet)
499     [NSApp setMainMenu: menu];
501   [pool release];
502   UNBLOCK_INPUT;
507 /* Main emacs core entry point for menubar menus: called to indicate that the
508    frame's menus have changed, and the *step representation should be updated
509    from Lisp. */
510 void
511 set_frame_menubar (struct frame *f, int first_time, int deep_p)
513   ns_update_menubar (f, deep_p, nil);
517 /* ==========================================================================
519     Menu: class implementation
521    ========================================================================== */
524 /* Menu that can define itself from Emacs "widget_value"s and will lazily
525    update itself when user clicked.  Based on Carbon/AppKit implementation
526    by Yamamoto Mitsuharu. */
527 @implementation EmacsMenu
529 /* override designated initializer */
530 - initWithTitle: (NSString *)title
532   if ((self = [super initWithTitle: title]))
533     [self setAutoenablesItems: NO];
534   return self;
538 /* used for top-level */
539 - initWithTitle: (NSString *)title frame: (struct frame *)f
541   [self initWithTitle: title];
542   frame = f;
543 #ifdef NS_IMPL_COCOA
544   [self setDelegate: self];
545 #endif
546   return self;
550 - (void)setFrame: (struct frame *)f
552   frame = f;
555 #ifdef NS_IMPL_COCOA
556 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
557 extern NSString *NSMenuDidBeginTrackingNotification;
558 #endif
559 #endif
561 #ifdef NS_IMPL_COCOA
562 -(void)trackingNotification:(NSNotification *)notification
564   /* Update menu in menuNeedsUpdate only while tracking menus.  */
565   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
566                   ? 1 : 0);
568 #endif
570 /* delegate method called when a submenu is being opened: run a 'deep' call
571    to set_frame_menubar */
572 - (void)menuNeedsUpdate: (NSMenu *)menu
574   if (!FRAME_LIVE_P (frame))
575     return;
577   /* Cocoa/Carbon will request update on every keystroke
578      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
579      since key equivalents are handled through emacs.
580      On Leopard, even keystroke events generate SystemDefined event.
581      Third-party applications that enhance mouse / trackpad
582      interaction, or also VNC/Remote Desktop will send events
583      of type AppDefined rather than SysDefined.
584      Menus will fail to show up if they haven't been initialized.
585      AppDefined events may lack timing data.
587      Thus, we rely on the didBeginTrackingNotification notification
588      as above to indicate the need for updates.
589      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
590      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
591   */
592   if (trackingMenu == 0
593       /* Also, don't try this if from an event picked up asynchronously,
594          as lots of lisp evaluation happens in ns_update_menubar. */
595       || handling_signal != 0)
596     return;
597 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
598   ns_update_menubar (frame, 1, self);
602 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
604   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
605       && FRAME_NS_VIEW (SELECTED_FRAME ()))
606     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
607   return YES;
611 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
612    into an accelerator string.  We are only able to display a single character
613    for an accelerator, together with an optional modifier combination.  (Under
614    Carbon more control was possible, but in Cocoa multi-char strings passed to
615    NSMenuItem get ignored.  For now we try to display a super-single letter
616    combo, and return the others as strings to be appended to the item title.
617    (This is signaled by setting keyEquivModMask to 0 for now.) */
618 -(NSString *)parseKeyEquiv: (const char *)key
620   const char *tpos = key;
621   keyEquivModMask = NSCommandKeyMask;
623   if (!key || !strlen (key))
624     return @"";
626   while (*tpos == ' ' || *tpos == '(')
627     tpos++;
628   if ((*tpos == 's') && (*(tpos+1) == '-'))
629     {
630       return [NSString stringWithFormat: @"%c", tpos[2]];
631     }
632   keyEquivModMask = 0; /* signal */
633   return [NSString stringWithUTF8String: tpos];
637 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
639   NSMenuItem *item;
640   widget_value *wv = (widget_value *)wvptr;
642   if (menu_separator_name_p (wv->name))
643     {
644       item = [NSMenuItem separatorItem];
645       [self addItem: item];
646     }
647   else
648     {
649       NSString *title, *keyEq;
650       title = [NSString stringWithUTF8String: wv->name];
651       if (title == nil)
652         title = @"< ? >";  /* (get out in the open so we know about it) */
654       keyEq = [self parseKeyEquiv: wv->key];
655 #ifdef NS_IMPL_COCOA
656       /* OS X just ignores modifier strings longer than one character */
657       if (keyEquivModMask == 0)
658         title = [title stringByAppendingFormat: @" (%@)", keyEq];
659 #endif
661       item = [self addItemWithTitle: (NSString *)title
662                              action: @selector (menuDown:)
663                       keyEquivalent: keyEq];
664       [item setKeyEquivalentModifierMask: keyEquivModMask];
666       [item setEnabled: wv->enabled];
668       /* Draw radio buttons and tickboxes */
669       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
670                            wv->button_type == BUTTON_TYPE_RADIO))
671         [item setState: NSOnState];
672       else
673         [item setState: NSOffState];
675       [item setTag: (NSInteger)wv->call_data];
676     }
678   return item;
682 /* convenience */
683 -(void)clear
685   int n;
687   for (n = [self numberOfItems]-1; n >= 0; n--)
688     {
689       NSMenuItem *item = [self itemAtIndex: n];
690       NSString *title = [item title];
691       if (([title length] == 0  /* OSX 10.5 */
692            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
693            || [@"Apple" isEqualToString: title]) /* older */
694           && ![item isSeparatorItem])
695         continue;
696       [self removeItemAtIndex: n];
697     }
701 - (void)fillWithWidgetValue: (void *)wvptr
703   widget_value *wv = (widget_value *)wvptr;
705   /* clear existing contents */
706   [self setMenuChangedMessagesEnabled: NO];
707   [self clear];
709   /* add new contents */
710   for (; wv != NULL; wv = wv->next)
711     {
712       NSMenuItem *item = [self addItemWithWidgetValue: wv];
714       if (wv->contents)
715         {
716           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
718           [self setSubmenu: submenu forItem: item];
719           [submenu fillWithWidgetValue: wv->contents];
720           [submenu release];
721           [item setAction: nil];
722         }
723     }
725   [self setMenuChangedMessagesEnabled: YES];
726 #ifdef NS_IMPL_GNUSTEP
727   if ([[self window] isVisible])
728     [self sizeToFit];
729 #else
730 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_2
731   if ([self supermenu] == nil)
732     [self sizeToFit];
733 #endif
734 #endif
738 /* adds an empty submenu and returns it */
739 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
741   NSString *titleStr = [NSString stringWithUTF8String: title];
742   NSMenuItem *item = [self addItemWithTitle: titleStr
743                                      action: nil /*@selector (menuDown:) */
744                               keyEquivalent: @""];
745   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
746   [self setSubmenu: submenu forItem: item];
747   [submenu release];
748   return submenu;
751 /* run a menu in popup mode */
752 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
753                  keymaps: (int)keymaps
755   EmacsView *view = FRAME_NS_VIEW (f);
756   NSEvent *e, *event;
757   long retVal;
759 /*   p = [view convertPoint:p fromView: nil]; */
760   p.y = NSHeight ([view frame]) - p.y;
761   e = [[view window] currentEvent];
762    event = [NSEvent mouseEventWithType: NSRightMouseDown
763                               location: p
764                          modifierFlags: 0
765                              timestamp: [e timestamp]
766                           windowNumber: [[view window] windowNumber]
767                                context: [e context]
768                            eventNumber: 0/*[e eventNumber] */
769                             clickCount: 1
770                               pressure: 0];
772   context_menu_value = -1;
773   [NSMenu popUpContextMenu: self withEvent: event forView: view];
774   retVal = context_menu_value;
775   context_menu_value = 0;
776   return retVal > 0
777       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
778       : Qnil;
781 @end  /* EmacsMenu */
785 /* ==========================================================================
787     Context Menu: implementing functions
789    ========================================================================== */
791 Lisp_Object
792 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
793               Lisp_Object title, const char **error)
795   EmacsMenu *pmenu;
796   NSPoint p;
797   Lisp_Object tem;
798   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
799   widget_value *wv, *first_wv = 0;
801   p.x = x; p.y = y;
803   /* now parse stage 2 as in ns_update_menubar */
804   wv = xmalloc_widget_value ();
805   wv->name = "contextmenu";
806   wv->value = 0;
807   wv->enabled = 1;
808   wv->button_type = BUTTON_TYPE_NONE;
809   wv->help = Qnil;
810   first_wv = wv;
812 #if 0
813   /* FIXME: a couple of one-line differences prevent reuse */
814   wv = digest_single_submenu (0, menu_items_used, Qnil);
815 #else
816   {
817   widget_value *save_wv = 0, *prev_wv = 0;
818   widget_value **submenu_stack
819     = alloca (menu_items_used * sizeof *submenu_stack);
820 /*   Lisp_Object *subprefix_stack
821        = alloca (menu_items_used * sizeof *subprefix_stack); */
822   int submenu_depth = 0;
823   int first_pane = 1;
824   int i;
826   /* Loop over all panes and items, filling in the tree.  */
827   i = 0;
828   while (i < menu_items_used)
829     {
830       if (EQ (AREF (menu_items, i), Qnil))
831         {
832           submenu_stack[submenu_depth++] = save_wv;
833           save_wv = prev_wv;
834           prev_wv = 0;
835           first_pane = 1;
836           i++;
837         }
838       else if (EQ (AREF (menu_items, i), Qlambda))
839         {
840           prev_wv = save_wv;
841           save_wv = submenu_stack[--submenu_depth];
842           first_pane = 0;
843           i++;
844         }
845       else if (EQ (AREF (menu_items, i), Qt)
846                && submenu_depth != 0)
847         i += MENU_ITEMS_PANE_LENGTH;
848       /* Ignore a nil in the item list.
849          It's meaningful only for dialog boxes.  */
850       else if (EQ (AREF (menu_items, i), Qquote))
851         i += 1;
852       else if (EQ (AREF (menu_items, i), Qt))
853         {
854           /* Create a new pane.  */
855           Lisp_Object pane_name, prefix;
856           const char *pane_string;
858           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
859           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
861 #ifndef HAVE_MULTILINGUAL_MENU
862           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
863             {
864               pane_name = ENCODE_MENU_STRING (pane_name);
865               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
866             }
867 #endif
868           pane_string = (NILP (pane_name)
869                          ? "" : SSDATA (pane_name));
870           /* If there is just one top-level pane, put all its items directly
871              under the top-level menu.  */
872           if (menu_items_n_panes == 1)
873             pane_string = "";
875           /* If the pane has a meaningful name,
876              make the pane a top-level menu item
877              with its items as a submenu beneath it.  */
878           if (!keymaps && strcmp (pane_string, ""))
879             {
880               wv = xmalloc_widget_value ();
881               if (save_wv)
882                 save_wv->next = wv;
883               else
884                 first_wv->contents = wv;
885               wv->name = pane_string;
886               if (keymaps && !NILP (prefix))
887                 wv->name++;
888               wv->value = 0;
889               wv->enabled = 1;
890               wv->button_type = BUTTON_TYPE_NONE;
891               wv->help = Qnil;
892               save_wv = wv;
893               prev_wv = 0;
894             }
895           else if (first_pane)
896             {
897               save_wv = wv;
898               prev_wv = 0;
899             }
900           first_pane = 0;
901           i += MENU_ITEMS_PANE_LENGTH;
902         }
903       else
904         {
905           /* Create a new item within current pane.  */
906           Lisp_Object item_name, enable, descrip, def, type, selected, help;
907           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
908           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
909           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
910           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
911           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
912           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
913           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
915 #ifndef HAVE_MULTILINGUAL_MENU
916           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
917             {
918               item_name = ENCODE_MENU_STRING (item_name);
919               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
920             }
922           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
923             {
924               descrip = ENCODE_MENU_STRING (descrip);
925               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
926             }
927 #endif /* not HAVE_MULTILINGUAL_MENU */
929           wv = xmalloc_widget_value ();
930           if (prev_wv)
931             prev_wv->next = wv;
932           else
933             save_wv->contents = wv;
934           wv->name = SSDATA (item_name);
935           if (!NILP (descrip))
936             wv->key = SSDATA (descrip);
937           wv->value = 0;
938           /* If this item has a null value,
939              make the call_data null so that it won't display a box
940              when the mouse is on it.  */
941           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
942           wv->enabled = !NILP (enable);
944           if (NILP (type))
945             wv->button_type = BUTTON_TYPE_NONE;
946           else if (EQ (type, QCtoggle))
947             wv->button_type = BUTTON_TYPE_TOGGLE;
948           else if (EQ (type, QCradio))
949             wv->button_type = BUTTON_TYPE_RADIO;
950           else
951             emacs_abort ();
953           wv->selected = !NILP (selected);
955           if (! STRINGP (help))
956             help = Qnil;
958           wv->help = help;
960           prev_wv = wv;
962           i += MENU_ITEMS_ITEM_LENGTH;
963         }
964     }
965   }
966 #endif
968   if (!NILP (title))
969     {
970       widget_value *wv_title = xmalloc_widget_value ();
971       widget_value *wv_sep = xmalloc_widget_value ();
973       /* Maybe replace this separator with a bitmap or owner-draw item
974          so that it looks better.  Having two separators looks odd.  */
975       wv_sep->name = "--";
976       wv_sep->next = first_wv->contents;
977       wv_sep->help = Qnil;
979 #ifndef HAVE_MULTILINGUAL_MENU
980       if (STRING_MULTIBYTE (title))
981         title = ENCODE_MENU_STRING (title);
982 #endif
984       wv_title->name = SSDATA (title);
985       wv_title->enabled = NO;
986       wv_title->button_type = BUTTON_TYPE_NONE;
987       wv_title->help = Qnil;
988       wv_title->next = wv_sep;
989       first_wv->contents = wv_title;
990     }
992   pmenu = [[EmacsMenu alloc] initWithTitle:
993                                [NSString stringWithUTF8String: SSDATA (title)]];
994   [pmenu fillWithWidgetValue: first_wv->contents];
995   free_menubar_widget_value_tree (first_wv);
996   unbind_to (specpdl_count, Qnil);
998   popup_activated_flag = 1;
999   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1000   popup_activated_flag = 0;
1001   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1003   return tem;
1007 /* ==========================================================================
1009     Toolbar: externally-called functions
1011    ========================================================================== */
1013 void
1014 free_frame_tool_bar (FRAME_PTR f)
1015 /* --------------------------------------------------------------------------
1016     Under NS we just hide the toolbar until it might be needed again.
1017    -------------------------------------------------------------------------- */
1019   BLOCK_INPUT;
1020   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1021   FRAME_TOOLBAR_HEIGHT (f) = 0;
1022   UNBLOCK_INPUT;
1025 void
1026 update_frame_tool_bar (FRAME_PTR f)
1027 /* --------------------------------------------------------------------------
1028     Update toolbar contents
1029    -------------------------------------------------------------------------- */
1031   int i;
1032   EmacsView *view = FRAME_NS_VIEW (f);
1033   NSWindow *window = [view window];
1034   EmacsToolbar *toolbar = [view toolbar];
1036   BLOCK_INPUT;
1037   [toolbar clearActive];
1039   /* update EmacsToolbar as in GtkUtils, build items list */
1040   for (i = 0; i < f->n_tool_bar_items; ++i)
1041     {
1042 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1043                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1045       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1046       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           eassert (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       id 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    ========================================================================== */
1350 struct Popdown_data
1352   NSAutoreleasePool *pool;
1353   EmacsDialogPanel *dialog;
1356 static Lisp_Object
1357 pop_down_menu (Lisp_Object arg)
1359   struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1360   struct Popdown_data *unwind_data = (struct Popdown_data *) p->pointer;
1362   BLOCK_INPUT;
1363   if (popup_activated_flag)
1364     {
1365       EmacsDialogPanel *panel = unwind_data->dialog;
1366       popup_activated_flag = 0;
1367       [panel close];
1368       [unwind_data->pool release];
1369       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1370     }
1372   xfree (unwind_data);
1373   UNBLOCK_INPUT;
1375   return Qnil;
1379 Lisp_Object
1380 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1382   id dialog;
1383   Lisp_Object window, tem, title;
1384   struct frame *f;
1385   NSPoint p;
1386   BOOL isQ;
1387   NSAutoreleasePool *pool;
1389   NSTRACE (x-popup-dialog);
1391   check_ns ();
1393   isQ = NILP (header);
1395   if (EQ (position, Qt)
1396       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1397                                || EQ (XCAR (position), Qtool_bar))))
1398     {
1399       window = selected_window;
1400     }
1401   else if (CONSP (position))
1402     {
1403       Lisp_Object tem;
1404       tem = Fcar (position);
1405       if (XTYPE (tem) == Lisp_Cons)
1406         window = Fcar (Fcdr (position));
1407       else
1408         {
1409           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1410           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1411         }
1412     }
1413   else if (WINDOWP (position) || FRAMEP (position))
1414     {
1415       window = position;
1416     }
1417   else
1418     window = Qnil;
1420   if (FRAMEP (window))
1421     f = XFRAME (window);
1422   else if (WINDOWP (window))
1423     {
1424       CHECK_LIVE_WINDOW (window);
1425       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1426     }
1427   else
1428     CHECK_WINDOW (window);
1430   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1431   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1433   title = Fcar (contents);
1434   CHECK_STRING (title);
1436   if (NILP (Fcar (Fcdr (contents))))
1437     /* No buttons specified, add an "Ok" button so users can pop down
1438        the dialog.  */
1439     contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1441   BLOCK_INPUT;
1442   pool = [[NSAutoreleasePool alloc] init];
1443   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1444                                            isQuestion: isQ];
1446   {
1447     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1448     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1450     unwind_data->pool = pool;
1451     unwind_data->dialog = dialog;
1453     record_unwind_protect (pop_down_menu, make_save_value (unwind_data, 0));
1454     popup_activated_flag = 1;
1455     tem = [dialog runDialogAt: p];
1456     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1457   }
1459   UNBLOCK_INPUT;
1461   return tem;
1465 /* ==========================================================================
1467     Popup Dialog: class implementation
1469    ========================================================================== */
1471 @interface FlippedView : NSView
1474 @end
1476 @implementation FlippedView
1477 - (BOOL)isFlipped
1479   return YES;
1481 @end
1483 @implementation EmacsDialogPanel
1485 #define SPACER          8.0
1486 #define ICONSIZE        64.0
1487 #define TEXTHEIGHT      20.0
1488 #define MINCELLWIDTH    90.0
1490 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1491               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1493   NSSize spacing = {SPACER, SPACER};
1494   NSRect area;
1495   id cell;
1496   NSImageView *imgView;
1497   FlippedView *contentView;
1498   NSImage *img;
1500   dialog_return   = Qundefined;
1501   button_values   = NULL;
1502   area.origin.x   = 3*SPACER;
1503   area.origin.y   = 2*SPACER;
1504   area.size.width = ICONSIZE;
1505   area.size.height= ICONSIZE;
1506   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1507   [img setScalesWhenResized: YES];
1508   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1509   imgView = [[NSImageView alloc] initWithFrame: area];
1510   [imgView setImage: img];
1511   [imgView setEditable: NO];
1512   [img autorelease];
1513   [imgView autorelease];
1515   aStyle = NSTitledWindowMask;
1516   flag = YES;
1517   rows = 0;
1518   cols = 1;
1519   [super initWithContentRect: contentRect styleMask: aStyle
1520                      backing: backingType defer: flag];
1521   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1522   [contentView autorelease];
1524   [self setContentView: contentView];
1526   [[self contentView] setAutoresizesSubviews: YES];
1528   [[self contentView] addSubview: imgView];
1529   [self setTitle: @""];
1531   area.origin.x   += ICONSIZE+2*SPACER;
1532 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1533   area.size.width = 400;
1534   area.size.height= TEXTHEIGHT;
1535   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1536   [[self contentView] addSubview: command];
1537   [command setStringValue: ns_app_name];
1538   [command setDrawsBackground: NO];
1539   [command setBezeled: NO];
1540   [command setSelectable: NO];
1541   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1543 /*  area.origin.x   = ICONSIZE+2*SPACER;
1544   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1545   area.size.width = 400;
1546   area.size.height= 2;
1547   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1548   [[self contentView] addSubview: tem];
1549   [tem setTitlePosition: NSNoTitle];
1550   [tem setAutoresizingMask: NSViewWidthSizable];*/
1552 /*  area.origin.x = ICONSIZE+2*SPACER; */
1553   area.origin.y += TEXTHEIGHT+SPACER;
1554   area.size.width = 400;
1555   area.size.height= TEXTHEIGHT;
1556   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1557   [[self contentView] addSubview: title];
1558   [title setDrawsBackground: NO];
1559   [title setBezeled: NO];
1560   [title setSelectable: NO];
1561   [title setFont: [NSFont systemFontOfSize: 11.0]];
1563   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1564   [cell setBordered: NO];
1565   [cell setEnabled: NO];
1566   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1567   [cell setBezelStyle: NSRoundedBezelStyle];
1569   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1570                                       mode: NSHighlightModeMatrix
1571                                  prototype: cell
1572                               numberOfRows: 0
1573                            numberOfColumns: 1];
1574   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1575                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1576   [matrix setIntercellSpacing: spacing];
1577   [matrix autorelease];
1579   [[self contentView] addSubview: matrix];
1580   [self setOneShot: YES];
1581   [self setReleasedWhenClosed: YES];
1582   [self setHidesOnDeactivate: YES];
1583   [self setStyleMask:
1584           NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask];
1586   return self;
1590 - (BOOL)windowShouldClose: (id)sender
1592   window_closed = YES;
1593   [NSApp stop:self];
1594   return NO;
1597 - (void)dealloc
1599   xfree (button_values);
1600   [super dealloc];
1603 - (void)process_dialog: (Lisp_Object) list
1605   Lisp_Object item, lst = list;
1606   int row = 0;
1607   int buttons = 0, btnnr = 0;
1609   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1610     {
1611       item = XCAR (list);
1612       if (XTYPE (item) == Lisp_Cons)
1613         ++buttons;
1614     }
1616   if (buttons > 0)
1617     button_values = (Lisp_Object *) xmalloc (buttons * sizeof (*button_values));
1619   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1620     {
1621       item = XCAR (list);
1622       if (XTYPE (item) == Lisp_String)
1623         {
1624           [self addString: SSDATA (item) row: row++];
1625         }
1626       else if (XTYPE (item) == Lisp_Cons)
1627         {
1628           button_values[btnnr] = XCDR (item);
1629           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1630           ++btnnr;
1631         }
1632       else if (NILP (item))
1633         {
1634           [self addSplit];
1635           row = 0;
1636         }
1637     }
1641 - (void)addButton: (char *)str value: (int)tag row: (int)row
1643   id cell;
1645   if (row >= rows)
1646     {
1647       [matrix addRow];
1648       rows++;
1649     }
1650   cell = [matrix cellAtRow: row column: cols-1];
1651   [cell setTarget: self];
1652   [cell setAction: @selector (clicked: )];
1653   [cell setTitle: [NSString stringWithUTF8String: str]];
1654   [cell setTag: tag];
1655   [cell setBordered: YES];
1656   [cell setEnabled: YES];
1660 - (void)addString: (char *)str row: (int)row
1662   id cell;
1664   if (row >= rows)
1665     {
1666       [matrix addRow];
1667       rows++;
1668     }
1669   cell = [matrix cellAtRow: row column: cols-1];
1670   [cell setTitle: [NSString stringWithUTF8String: str]];
1671   [cell setBordered: YES];
1672   [cell setEnabled: NO];
1676 - (void)addSplit
1678   [matrix addColumn];
1679   cols++;
1683 - (void)clicked: sender
1685   NSArray *sellist = nil;
1686   EMACS_INT seltag;
1688   sellist = [sender selectedCells];
1689   if ([sellist count] < 1)
1690     return;
1692   seltag = [[sellist objectAtIndex: 0] tag];
1693   dialog_return = button_values[seltag];
1694   [NSApp stop:self];
1698 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1700   Lisp_Object head;
1701   [super init];
1703   if (XTYPE (contents) == Lisp_Cons)
1704     {
1705       head = Fcar (contents);
1706       [self process_dialog: Fcdr (contents)];
1707     }
1708   else
1709     head = contents;
1711   if (XTYPE (head) == Lisp_String)
1712       [title setStringValue:
1713                  [NSString stringWithUTF8String: SSDATA (head)]];
1714   else if (isQ == YES)
1715       [title setStringValue: @"Question"];
1716   else
1717       [title setStringValue: @"Information"];
1719   {
1720     int i;
1721     NSRect r, s, t;
1723     if (cols == 1 && rows > 1)  /* Never told where to split */
1724       {
1725         [matrix addColumn];
1726         for (i = 0; i < rows/2; i++)
1727           {
1728             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1729                       atRow: i column: 1];
1730             [matrix removeRow: (rows+1)/2];
1731           }
1732       }
1734     [matrix sizeToFit];
1735     {
1736       NSSize csize = [matrix cellSize];
1737       if (csize.width < MINCELLWIDTH)
1738         {
1739           csize.width = MINCELLWIDTH;
1740           [matrix setCellSize: csize];
1741           [matrix sizeToCells];
1742         }
1743     }
1745     [title sizeToFit];
1746     [command sizeToFit];
1748     t = [matrix frame];
1749     r = [title frame];
1750     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1751       {
1752         t.origin.x   = r.origin.x;
1753         t.size.width = r.size.width;
1754       }
1755     r = [command frame];
1756     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1757       {
1758         t.origin.x   = r.origin.x;
1759         t.size.width = r.size.width;
1760       }
1762     r = [self frame];
1763     s = [(NSView *)[self contentView] frame];
1764     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1765     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1766     [self setFrame: r display: NO];
1767   }
1769   return self;
1774 - (void)timeout_handler: (NSTimer *)timedEntry
1776   NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1777                             location: NSMakePoint (0, 0)
1778                        modifierFlags: 0
1779                            timestamp: 0
1780                         windowNumber: [[NSApp mainWindow] windowNumber]
1781                              context: [NSApp context]
1782                              subtype: 0
1783                                data1: 0
1784                                data2: 0];
1786   timer_fired = YES;
1787   /* We use sto because stopModal/abortModal out of the main loop does not
1788      seem to work in 10.6.  But as we use stop we must send a real event so
1789      the stop is seen and acted upon.  */
1790   [NSApp stop:self];
1791   [NSApp postEvent: nxev atStart: NO];
1794 - (Lisp_Object)runDialogAt: (NSPoint)p
1796   Lisp_Object ret = Qundefined;
1798   while (popup_activated_flag)
1799     {
1800       NSTimer *tmo = nil;
1801       EMACS_TIME next_time = timer_check ();
1803       if (EMACS_TIME_VALID_P (next_time))
1804         {
1805           double time = EMACS_TIME_TO_DOUBLE (next_time);
1806           tmo = [NSTimer timerWithTimeInterval: time
1807                                         target: self
1808                                       selector: @selector (timeout_handler:)
1809                                       userInfo: 0
1810                                        repeats: NO];
1811           [[NSRunLoop currentRunLoop] addTimer: tmo
1812                                        forMode: NSModalPanelRunLoopMode];
1813         }
1814       timer_fired = NO;
1815       dialog_return = Qundefined;
1816       [NSApp runModalForWindow: self];
1817       ret = dialog_return;
1818       if (! timer_fired)
1819         {
1820           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1821           break;
1822         }
1823     }
1825   if (EQ (ret, Qundefined) && window_closed)
1826     /* Make close button pressed equivalent to C-g.  */
1827     Fsignal (Qquit, Qnil);
1829   return ret;
1832 @end
1835 /* ==========================================================================
1837     Lisp definitions
1839    ========================================================================== */
1841 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1842        doc: /* Cause the NS menu to be re-calculated.  */)
1843      (void)
1845   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1846   return Qnil;
1850 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1851        doc: /* Pop up a dialog box and return user's selection.
1852 POSITION specifies which frame to use.
1853 This is normally a mouse button event or a window or frame.
1854 If POSITION is t, it means to use the frame the mouse is on.
1855 The dialog box appears in the middle of the specified frame.
1857 CONTENTS specifies the alternatives to display in the dialog box.
1858 It is a list of the form (DIALOG ITEM1 ITEM2...).
1859 Each ITEM is a cons cell (STRING . VALUE).
1860 The return value is VALUE from the chosen item.
1862 An ITEM may also be just a string--that makes a nonselectable item.
1863 An ITEM may also be nil--that means to put all preceding items
1864 on the left of the dialog box and all following items on the right.
1865 \(By default, approximately half appear on each side.)
1867 If HEADER is non-nil, the frame title for the box is "Information",
1868 otherwise it is "Question".
1870 If the user gets rid of the dialog box without making a valid choice,
1871 for instance using the window manager, then this produces a quit and
1872 `x-popup-dialog' does not return.  */)
1873      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1875   return ns_popup_dialog (position, contents, header);
1878 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1879        doc: /* Return t if a menu or popup dialog is active.  */)
1880      (void)
1882   return popup_activated () ? Qt : Qnil;
1885 /* ==========================================================================
1887     Lisp interface declaration
1889    ========================================================================== */
1891 void
1892 syms_of_nsmenu (void)
1894 #ifndef NS_IMPL_COCOA
1895   /* Don't know how to keep track of this in Next/Open/Gnustep.  Always
1896      update menus there.  */
1897   trackingMenu = 1;
1898 #endif
1899   defsubr (&Sx_popup_dialog);
1900   defsubr (&Sns_reset_menu);
1901   defsubr (&Smenu_or_popup_active_p);
1903   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1904   staticpro (&Qdebug_on_next_call);