Fix copyright years and license notices.
[emacs.git] / src / nsmenu.m
blob825568f6557759e834481b33d6a74e7eb69b321e
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007, 2008 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 #include "config.h"
25 #include "lisp.h"
26 #include "window.h"
27 #include "buffer.h"
28 #include "keymap.h"
29 #include "coding.h"
30 #include "commands.h"
31 #include "blockinput.h"
32 #include "nsterm.h"
33 #include "termhooks.h"
34 #include "keyboard.h"
36 /* for profiling */
37 #include <sys/timeb.h>
38 #include <sys/types.h>
40 #define MenuStagger 10.0
42 #if 0
43 int menu_trace_num = 0;
44 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
45                                 __FILE__, __LINE__, ++menu_trace_num)
46 #else
47 #define NSTRACE(x)
48 #endif
50 #if 0
51 /* Include lisp -> C common menu parsing code */
52 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
53 #include "nsmenu_common.c"
54 #endif
56 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
57 extern Lisp_Object QCtoggle, QCradio;
59 extern Lisp_Object Vmenu_updating_frame;
61 Lisp_Object Qdebug_on_next_call;
62 extern Lisp_Object Voverriding_local_map, Voverriding_local_map_menu_flag,
63                    Qoverriding_local_map, Qoverriding_terminal_local_map;
65 extern long context_menu_value;
66 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
68 /* Nonzero means a menu is currently active.  */
69 static int popup_activated_flag;
71 /* NOTE: toolbar implementation is at end,
72   following complete menu implementation. */
75 /* ==========================================================================
77     Menu: Externally-called functions
79    ========================================================================== */
82 /*23: FIXME: not currently used, but should normalize with other terms. */
83 void
84 x_activate_menubar (struct frame *f)
86     fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
90 /* Supposed to discard menubar and free storage.  Since we share the
91    menubar among frames and update its context for the focused window,
92    there is nothing to do here. */
93 void
94 free_frame_menubar (struct frame *f)
96   return;
101 popup_activated ()
103   return popup_activated_flag;
107 /* --------------------------------------------------------------------------
108     Update menubar.  Three cases:
109     1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
110        just top-level menu strings (OS X), or goto case (2) (GNUstep).
111     2) deep_p = 1, submenu = nil: Recompute all submenus.
112     3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
113    -------------------------------------------------------------------------- */
114 /*#define NSMENUPROFILE 1 */
115 void
116 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
118   NSAutoreleasePool *pool;
119   id menu = [NSApp mainMenu];
120   static EmacsMenu *last_submenu = nil;
121   BOOL needsSet = NO;
122   const char *submenuTitle = [[submenu title] UTF8String];
123   extern int waiting_for_input;
124   int owfi;
125   Lisp_Object items;
126   widget_value *wv, *first_wv, *prev_wv = 0;
127   int i;
129 #ifdef NSMENUPROFILE
130   struct timeb tb;
131   long t;
132 #endif
134   NSTRACE (set_frame_menubar);
136   if (f != SELECTED_FRAME ())
137       return;
138   XSETFRAME (Vmenu_updating_frame, f);
139 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
141   BLOCK_INPUT;
142   pool = [[NSAutoreleasePool alloc] init];
144   /* Menu may have been created automatically; if so, discard it. */
145   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
146     {
147       [menu release];
148       menu = nil;
149     }
151   if (menu == nil)
152     {
153       menu = [[EmacsMenu alloc] initWithTitle: @"Emacs"];
154       needsSet = YES;
155     }
156   else
157     {  /* close up anything on there */
158       id attMenu = [menu attachedMenu];
159       if (attMenu != nil)
160         [attMenu close];
161     }
163 #ifdef NSMENUPROFILE
164   ftime (&tb);
165   t = -(1000*tb.time+tb.millitm);
166 #endif
168   /* widget_value is a straightforward object translation of emacs's
169      Byzantine lisp menu structures */
170   wv = xmalloc_widget_value ();
171   wv->name = "Emacs";
172   wv->value = 0;
173   wv->enabled = 1;
174   wv->button_type = BUTTON_TYPE_NONE;
175   wv->help = Qnil;
176   first_wv = wv;
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       int specpdl_count = SPECPDL_INDEX ();
191       int previous_menu_items_used = f->menu_bar_items_used;
192       Lisp_Object *previous_items
193         = (Lisp_Object *) alloca (previous_menu_items_used
194                                   * sizeof (Lisp_Object));
196       /* lisp preliminaries */
197       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
198       specbind (Qinhibit_quit, Qt);
199       specbind (Qdebug_on_next_call, Qnil);
200       record_unwind_save_match_data ();
201       if (NILP (Voverriding_local_map_menu_flag))
202         {
203           specbind (Qoverriding_terminal_local_map, Qnil);
204           specbind (Qoverriding_local_map, Qnil);
205         }
206       set_buffer_internal_1 (XBUFFER (buffer));
208       /* TODO: for some reason this is not needed in other terms,
209            but some menu updates call Info-extract-pointer which causes
210            abort-on-error if waiting-for-input.  Needs further investigation. */
211       owfi = waiting_for_input;
212       waiting_for_input = 0;
214       /* lucid hook and possible reset */
215       safe_run_hooks (Qactivate_menubar_hook);
216       if (! NILP (Vlucid_menu_bar_dirty_flag))
217         call0 (Qrecompute_lucid_menubar);
218       safe_run_hooks (Qmenu_bar_update_hook);
219       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
221       /* Now ready to go */
222       items = FRAME_MENU_BAR_ITEMS (f);
224       /* Save the frame's previous menu bar contents data */
225       if (previous_menu_items_used)
226         bcopy (XVECTOR (f->menu_bar_vector)->contents, previous_items,
227                previous_menu_items_used * sizeof (Lisp_Object));
229       /* parse stage 1: extract from lisp */
230       save_menu_items ();
232       menu_items = f->menu_bar_vector;
233       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
234       submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
235       submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
236       submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
237       submenu_top_level_items
238         = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
239       init_menu_items ();
240       for (i = 0; i < XVECTOR (items)->size; i += 4)
241         {
242           Lisp_Object key, string, maps;
244           key = XVECTOR (items)->contents[i];
245           string = XVECTOR (items)->contents[i + 1];
246           maps = XVECTOR (items)->contents[i + 2];
247           if (NILP (string))
248             break;
250           /* FIXME: we'd like to only parse the needed submenu, but this
251                was causing crashes in the _common parsing code.. need to make
252                sure proper initialization done.. */
253 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
254              continue; */
256           submenu_start[i] = menu_items_used;
258           menu_items_n_panes = 0;
259           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
260           submenu_n_panes[i] = menu_items_n_panes;
261           submenu_end[i] = menu_items_used;
262           n++;
263         }
265       finish_menu_items ();
266       waiting_for_input = owfi;
269       if (submenu && n == 0)
270         {
271           /* should have found a menu for this one but didn't */
272           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
273                   submenuTitle);
274           discard_menu_items ();
275           unbind_to (specpdl_count, Qnil);
276           [pool release];
277           UNBLOCK_INPUT;
278           return;
279         }
281       /* parse stage 2: insert into lucid 'widget_value' structures
282          [comments in other terms say not to evaluate lisp code here] */
283       wv = xmalloc_widget_value ();
284       wv->name = "menubar";
285       wv->value = 0;
286       wv->enabled = 1;
287       wv->button_type = BUTTON_TYPE_NONE;
288       wv->help = Qnil;
289       first_wv = wv;
291       for (i = 0; i < 4*n; i += 4)
292         {
293           menu_items_n_panes = submenu_n_panes[i];
294           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
295                                       submenu_top_level_items[i]);
296           if (prev_wv)
297             prev_wv->next = wv;
298           else
299             first_wv->contents = wv;
300           /* Don't set wv->name here; GC during the loop might relocate it.  */
301           wv->enabled = 1;
302           wv->button_type = BUTTON_TYPE_NONE;
303           prev_wv = wv;
304         }
306       set_buffer_internal_1 (prev);
308       /* Compare the new menu items with previous, and leave off if no change */
309       /* FIXME: following other terms here, but seems like this should be
310            done before parse stage 2 above, since its results aren't used */
311       if (previous_menu_items_used
312           && (!submenu || (submenu && submenu == last_submenu))
313           && menu_items_used == previous_menu_items_used)
314         {
315           for (i = 0; i < previous_menu_items_used; i++)
316             /* FIXME: this ALWAYS fails on Buffers menu items.. something
317                  about their strings causes them to change every time, so we
318                  double-check failures */
319             if (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i]))
320               if (!(STRINGP (previous_items[i])
321                     && STRINGP (XVECTOR (menu_items)->contents[i])
322                     && !strcmp (SDATA (previous_items[i]),
323                                SDATA (XVECTOR (menu_items)->contents[i]))))
324                   break;
325           if (i == previous_menu_items_used)
326             {
327               /* No change.. */
329 #ifdef NSMENUPROFILE
330               ftime (&tb);
331               t += 1000*tb.time+tb.millitm;
332               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
333 #endif
335               free_menubar_widget_value_tree (first_wv);
336               discard_menu_items ();
337               unbind_to (specpdl_count, Qnil);
338               [pool release];
339               UNBLOCK_INPUT;
340               return;
341             }
342         }
343       /* The menu items are different, so store them in the frame */
344       /* FIXME: this is not correct for single-submenu case */
345       f->menu_bar_vector = menu_items;
346       f->menu_bar_items_used = menu_items_used;
348       /* Calls restore_menu_items, etc., as they were outside */
349       unbind_to (specpdl_count, Qnil);
351       /* Parse stage 2a: now GC cannot happen during the lifetime of the
352          widget_value, so it's safe to store data from a Lisp_String */
353       wv = first_wv->contents;
354       for (i = 0; i < XVECTOR (items)->size; i += 4)
355         {
356           Lisp_Object string;
357           string = XVECTOR (items)->contents[i + 1];
358           if (NILP (string))
359             break;
360 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
361                continue; */
363           wv->name = (char *) SDATA (string);
364           update_submenu_strings (wv->contents);
365           wv = wv->next;
366         }
368       /* Now, update the NS menu; if we have a submenu, use that, otherwise
369          create a new menu for each sub and fill it. */
370       if (submenu)
371         {
372           for (wv = first_wv->contents; wv; wv = wv->next)
373             {
374               if (!strcmp (submenuTitle, wv->name))
375                 {
376                   [submenu fillWithWidgetValue: wv->contents];
377                   last_submenu = submenu;
378                   break;
379                 }
380             }
381         }
382       else
383         {
384           [menu fillWithWidgetValue: first_wv->contents];
385         }
387     }
388   else
389     {
390       static int n_previous_strings = 0;
391       static char previous_strings[100][10];
392       static struct frame *last_f = NULL;
393       int n;
394       Lisp_Object string;
396       /* Make widget-value tree w/ just the top level menu bar strings */
397       items = FRAME_MENU_BAR_ITEMS (f);
398       if (NILP (items))
399         {
400           [pool release];
401           UNBLOCK_INPUT;
402           return;
403         }
406       /* check if no change.. this mechanism is a bit rough, but ready */
407       n = XVECTOR (items)->size / 4;
408       if (f == last_f && n_previous_strings == n)
409         {
410           for (i = 0; i<n; i++)
411             {
412               string = AREF (items, 4*i+1);
414               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
415                 continue;
416               if (NILP (string))
417                 if (previous_strings[i][0])
418                   break;
419               else
420                 continue;
421               if (strncmp (previous_strings[i], SDATA (string), 10))
422                 break;
423             }
425           if (i == n)
426             {
427               [pool release];
428               UNBLOCK_INPUT;
429               return;
430             }
431         }
433       [menu clear];
434       for (i = 0; i < XVECTOR (items)->size; i += 4)
435         {
436           string = XVECTOR (items)->contents[i + 1];
437           if (NILP (string))
438             break;
440           if (n < 100)
441             strncpy (previous_strings[i/4], SDATA (string), 10);
443           wv = xmalloc_widget_value ();
444           wv->name = (char *) SDATA (string);
445           wv->value = 0;
446           wv->enabled = 1;
447           wv->button_type = BUTTON_TYPE_NONE;
448           wv->help = Qnil;
449           wv->call_data = (void *) (EMACS_INT) (-1);
451 #ifdef NS_IMPL_COCOA
452           /* we'll update the real copy under app menu when time comes */
453           if (!strcmp ("Services", wv->name))
454             {
455               /* but we need to make sure it will update on demand */
456               [svcsMenu setFrame: f];
457               [svcsMenu setDelegate: svcsMenu];
458             }
459           else
460 #endif
461           [menu addSubmenuWithTitle: wv->name forFrame: f];
463           if (prev_wv)
464             prev_wv->next = wv;
465           else
466             first_wv->contents = wv;
467           prev_wv = wv;
468         }
470       last_f = f;
471       if (n < 100)
472         n_previous_strings = n;
473       else
474         n_previous_strings = 0;
476     }
477   free_menubar_widget_value_tree (first_wv);
480 #ifdef NSMENUPROFILE
481   ftime (&tb);
482   t += 1000*tb.time+tb.millitm;
483   fprintf (stderr, "Menu update took %ld msec.\n", t);
484 #endif
486   /* set main menu */
487   if (needsSet)
488     [NSApp setMainMenu: menu];
490   [pool release];
491   UNBLOCK_INPUT;
496 /* Main emacs core entry point for menubar menus: called to indicate that the
497    frame's menus have changed, and the *step representation should be updated
498    from Lisp. */
499 void
500 set_frame_menubar (struct frame *f, int first_time, int deep_p)
502   ns_update_menubar (f, deep_p, nil);
506 /* Utility (from macmenu.c): is this item a separator? */
507 static int
508 name_is_separator (name)
509      const char *name;
511   const char *start = name;
513   /* Check if name string consists of only dashes ('-').  */
514   while (*name == '-') name++;
515   /* Separators can also be of the form "--:TripleSuperMegaEtched"
516      or "--deep-shadow".  We don't implement them yet, se we just treat
517      them like normal separators.  */
518   return (*name == '\0' || start + 2 == name);
522 /* ==========================================================================
524     Menu: class implementation
526    ========================================================================== */
529 /* Menu that can define itself from Emacs "widget_value"s and will lazily
530    update itself when user clicked.  Based on Carbon/AppKit implementation
531    by Yamamoto Mitsuharu. */
532 @implementation EmacsMenu
534 /* override designated initializer */
535 - initWithTitle: (NSString *)title
537   if (self = [super initWithTitle: title])
538     [self setAutoenablesItems: NO];
539   return self;
543 /* used for top-level */
544 - initWithTitle: (NSString *)title frame: (struct frame *)f
546   [self initWithTitle: title];
547   frame = f;
548 #ifdef NS_IMPL_COCOA
549   [self setDelegate: self];
550 #endif
551   return self;
555 - (void)setFrame: (struct frame *)f
557   frame = f;
561 /* delegate method called when a submenu is being opened: run a 'deep' call
562    to set_frame_menubar */
563 - (void)menuNeedsUpdate: (NSMenu *)menu
565   NSEvent *event = [[FRAME_NS_VIEW (frame) window] currentEvent];
566   /* HACK: Cocoa/Carbon will request update on every keystroke
567      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
568      since key equivalents are handled through emacs.
569      On Leopard, even keystroke events generate SystemDefined events, but
570      their subtype is 8. */
571   if ([event type] != NSSystemDefined || [event subtype] == 8)
572     return;
573 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
574   ns_update_menubar (frame, 1, self);
578 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
580   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
581       && FRAME_NS_VIEW (SELECTED_FRAME ()))
582     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
583   return YES;
587 /* parse a wdiget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
588    into an accelerator string */
589 -(NSString *)parseKeyEquiv: (char *)key
591   char *tpos = key;
592   keyEquivModMask = 0;
593   /* currently we just parse 'super' combinations;
594      later we'll set keyEquivModMask */
595   if (!key || !strlen (key))
596     return @"";
597   
598   while (*tpos == ' ' || *tpos == '(')
599     tpos++;
600   if (*tpos != 's'/* || tpos[3] != ')'*/)
601     return @"";
602   return [NSString stringWithFormat: @"%c", tpos[2]];
606 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
608   NSMenuItem *item;
609   widget_value *wv = (widget_value *)wvptr;
611   if (name_is_separator (wv->name))
612     {
613       item = [NSMenuItem separatorItem];
614       [self addItem: item];
615     }
616   else
617     {
618       NSString *title, *keyEq;
619       title = [NSString stringWithUTF8String: wv->name];
620       if (title == nil)
621         title = @"< ? >";  /* (get out in the open so we know about it) */
623       keyEq = [self parseKeyEquiv: wv->key];
625       item = [self addItemWithTitle: (NSString *)title
626                              action: @selector (menuDown:)
627                       keyEquivalent: keyEq];
628       if (keyEquivModMask)
629         [item setKeyEquivalentModifierMask: keyEquivModMask];
631       [item setEnabled: wv->enabled];
633       /* Draw radio buttons and tickboxes */
634       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
635                            wv->button_type == BUTTON_TYPE_RADIO))
636         [item setState: NSOnState];
637       else
638         [item setState: NSOffState];
640       [item setTag: (int)wv->call_data];
641     }
643   return item;
647 /* convenience */
648 -(void) clear
650   int n;
651   
652   for (n = [self numberOfItems]-1; n >= 0; n--)
653     {
654       NSMenuItem *item = [self itemAtIndex: n];
655       NSString *title = [item title];
656       if (([title length] == 0 || [@"Apple" isEqualToString: title])
657           && ![item isSeparatorItem])
658         continue;
659       [self removeItemAtIndex: n];
660     }
664 - (void)fillWithWidgetValue: (void *)wvptr
666   widget_value *wv = (widget_value *)wvptr;
668   /* clear existing contents */
669   [self setMenuChangedMessagesEnabled: NO];
670   [self clear];
672   /* add new contents */
673   for (; wv != NULL; wv = wv->next)
674     {
675       NSMenuItem *item = [self addItemWithWidgetValue: wv];
677       if (wv->contents)
678         {
679           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: @"Submenu"];
681           [self setSubmenu: submenu forItem: item];
682           [submenu fillWithWidgetValue: wv->contents];
683           [submenu release];
684           [item setAction: nil];
685         }
686     }
688   [self setMenuChangedMessagesEnabled: YES];
689 #ifdef NS_IMPL_GNUSTEP
690   if ([[self window] isVisible])
691     [self sizeToFit];
692 #else
693   if ([self supermenu] == nil)
694     [self sizeToFit];
695 #endif
699 /* adds an empty submenu and returns it */
700 - (EmacsMenu *)addSubmenuWithTitle: (char *)title forFrame: (struct frame *)f
702   NSString *titleStr = [NSString stringWithUTF8String: title];
703   NSMenuItem *item = [self addItemWithTitle: titleStr
704                                      action: nil /*@selector (menuDown:) */
705                               keyEquivalent: @""];
706   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
707   [self setSubmenu: submenu forItem: item];
708   [submenu release];
709   return submenu;
712 /* run a menu in popup mode */
713 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
714                  keymaps: (int)keymaps
716   EmacsView *view = FRAME_NS_VIEW (f);
717 /*   p = [view convertPoint:p fromView: nil]; */
718   p.y = NSHeight ([view frame]) - p.y;
719   NSEvent *e = [[view window] currentEvent];
720   NSEvent *event = [NSEvent mouseEventWithType: NSRightMouseDown
721                                       location: p
722                                  modifierFlags: 0
723                                      timestamp: [e timestamp]
724                                   windowNumber: [[view window] windowNumber]
725                                        context: [e context]
726                                    eventNumber: 0/*[e eventNumber] */
727                                     clickCount: 1
728                                       pressure: 0];
729   long retVal;
731   context_menu_value = -1;
732   [NSMenu popUpContextMenu: self withEvent: event forView: view];
733   retVal = context_menu_value;
734   context_menu_value = 0;
735   return retVal > 0
736       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
737       : Qnil;
740 @end  /* EmacsMenu */
744 /* ==========================================================================
746     Context Menu: implementing functions
748    ========================================================================== */
750 static Lisp_Object
751 cleanup_popup_menu (Lisp_Object arg)
753   discard_menu_items ();
754   return Qnil;
758 static Lisp_Object
759 ns_popup_menu (Lisp_Object position, Lisp_Object menu)
761   EmacsMenu *pmenu;
762   struct frame *f = NULL;
763   NSPoint p;
764   Lisp_Object window, x, y, tem, keymap, title;
765   struct gcpro gcpro1;
766   int specpdl_count = SPECPDL_INDEX (), specpdl_count2;
767   char *error_name = NULL;
768   int keymaps = 0;
769   widget_value *wv, *first_wv = 0;
771   NSTRACE (ns_popup_menu);
773   if (!NILP (position))
774     {
775       check_ns ();
776   
777       if (EQ (position, Qt)
778           || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
779                                    || EQ (XCAR (position), Qtool_bar))))
780         {
781           /* Use the mouse's current position.  */
782           struct frame *new_f = SELECTED_FRAME ();
784           if (FRAME_TERMINAL (new_f)->mouse_position_hook)
785             (*FRAME_TERMINAL (new_f)->mouse_position_hook)
786               (&new_f, 0, 0, 0, &x, &y, 0);
787           if (new_f != 0)
788             XSETFRAME (window, new_f);
789           else
790             {
791               window = selected_window;
792               x = make_number (0);
793               y = make_number (0);
794             }
795         }
796       else
797         {
798           CHECK_CONS (position);
799           tem = Fcar (position);
800           if (XTYPE (tem) == Lisp_Cons)
801             {
802               window = Fcar (Fcdr (position));
803               x = Fcar (tem);
804               y = Fcar (Fcdr (tem));
805             }
806           else
807             {
808               tem = Fcar (Fcdr (position));
809               window = Fcar (tem);
810               tem = Fcar (Fcdr (Fcdr (tem)));
811               x = Fcar (tem);
812               y = Fcdr (tem);
813             }
814         }
815   
816       CHECK_NUMBER (x);
817       CHECK_NUMBER (y);
819       if (FRAMEP (window))
820         {
821           f = XFRAME (window);
822       
823           p.x = 0;
824           p.y = 0;
825         }
826       else
827         {
828           struct window *win = XWINDOW (window);
829           CHECK_LIVE_WINDOW (window);
830           f = XFRAME (WINDOW_FRAME (win));
831           p.x = FRAME_COLUMN_WIDTH (f) * WINDOW_LEFT_EDGE_COL (win);
832           p.y = FRAME_LINE_HEIGHT (f) * WINDOW_TOP_EDGE_LINE (win);
833         }
835       p.x += XINT (x); p.y += XINT (y);
837       XSETFRAME (Vmenu_updating_frame, f);
838     }
839   else
840     {      /* no position given */
841       /* FIXME: if called during dump, we need to stop precomputation of
842          key equivalents (see below) because the keydefs in ns-win.el have
843          not been loaded yet. */
844       if (noninteractive)
845         return Qnil;
846       Vmenu_updating_frame = Qnil;
847     }
849   /* now parse the lisp menus */
850   record_unwind_protect (unuse_menu_items, Qnil);
851   title = Qnil;
852   GCPRO1 (title);
854   /* Decode the menu items from what was specified.  */
856   keymap = get_keymap (menu, 0, 0);
857   if (CONSP (keymap))
858     {
859       /* We were given a keymap.  Extract menu info from the keymap.  */
860       Lisp_Object prompt;
862       /* Extract the detailed info to make one pane.  */
863       keymap_panes (&menu, 1, NILP (position));
865       /* Search for a string appearing directly as an element of the keymap.
866          That string is the title of the menu.  */
867       prompt = Fkeymap_prompt (keymap);
868       title = NILP (prompt) ? build_string ("Select") : prompt;
870       /* Make that be the pane title of the first pane.  */
871       if (!NILP (prompt) && menu_items_n_panes >= 0)
872         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt;
874       keymaps = 1;
875     }
876   else if (CONSP (menu) && KEYMAPP (XCAR (menu)))
877     {
878       /* We were given a list of keymaps.  */
879       int nmaps = XFASTINT (Flength (menu));
880       Lisp_Object *maps
881         = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object));
882       int i;
884       title = Qnil;
886       /* The first keymap that has a prompt string
887          supplies the menu title.  */
888       for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem))
889         {
890           Lisp_Object prompt;
892           maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0);
894           prompt = Fkeymap_prompt (keymap);
895           if (NILP (title) && !NILP (prompt))
896             title = prompt;
897         }
899       /* Extract the detailed info to make one pane.  */
900       keymap_panes (maps, nmaps, NILP (position));
902       /* Make the title be the pane title of the first pane.  */
903       if (!NILP (title) && menu_items_n_panes >= 0)
904         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title;
906       keymaps = 1;
907     }
908   else
909     {
910       /* We were given an old-fashioned menu.  */
911       title = Fcar (menu);
912       CHECK_STRING (title);
914       list_of_panes (Fcdr (menu));
916       keymaps = 0;
917     }
919   unbind_to (specpdl_count, Qnil);
921   /* If no position given, that was a signal to just precompute and cache
922      key equivalents, which was a side-effect of what we just did. */
923   if (NILP (position))
924     {
925       discard_menu_items ();
926       UNGCPRO;
927       return Qnil;
928     }
930   record_unwind_protect (cleanup_popup_menu, Qnil);
931   BLOCK_INPUT;
933   /* now parse stage 2 as in ns_update_menubar */
934   wv = xmalloc_widget_value ();
935   wv->name = "contextmenu";
936   wv->value = 0;
937   wv->enabled = 1;
938   wv->button_type = BUTTON_TYPE_NONE;
939   wv->help = Qnil;
940   first_wv = wv;
942   specpdl_count2 = SPECPDL_INDEX ();
944 #if 0
945   /* FIXME: a couple of one-line differences prevent reuse */
946   wv = digest_single_submenu (0, menu_items_used, Qnil);
947 #else
948   {
949   widget_value *save_wv = 0, *prev_wv = 0;
950   widget_value **submenu_stack
951     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
952 /*   Lisp_Object *subprefix_stack
953        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
954   int submenu_depth = 0;
955   int first_pane = 1;
956   int i;
958   /* Loop over all panes and items, filling in the tree.  */
959   i = 0;
960   while (i < menu_items_used)
961     {
962       if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
963         {
964           submenu_stack[submenu_depth++] = save_wv;
965           save_wv = prev_wv;
966           prev_wv = 0;
967           first_pane = 1;
968           i++;
969         }
970       else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
971         {
972           prev_wv = save_wv;
973           save_wv = submenu_stack[--submenu_depth];
974           first_pane = 0;
975           i++;
976         }
977       else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
978                && submenu_depth != 0)
979         i += MENU_ITEMS_PANE_LENGTH;
980       /* Ignore a nil in the item list.
981          It's meaningful only for dialog boxes.  */
982       else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
983         i += 1;
984       else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
985         {
986           /* Create a new pane.  */
987           Lisp_Object pane_name, prefix;
988           char *pane_string;
990           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
991           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
993 #ifndef HAVE_MULTILINGUAL_MENU
994           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
995             {
996               pane_name = ENCODE_MENU_STRING (pane_name);
997               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
998             }
999 #endif
1000           pane_string = (NILP (pane_name)
1001                          ? "" : (char *) SDATA (pane_name));
1002           /* If there is just one top-level pane, put all its items directly
1003              under the top-level menu.  */
1004           if (menu_items_n_panes == 1)
1005             pane_string = "";
1007           /* If the pane has a meaningful name,
1008              make the pane a top-level menu item
1009              with its items as a submenu beneath it.  */
1010           if (!keymaps && strcmp (pane_string, ""))
1011             {
1012               wv = xmalloc_widget_value ();
1013               if (save_wv)
1014                 save_wv->next = wv;
1015               else
1016                 first_wv->contents = wv;
1017               wv->name = pane_string;
1018               if (keymaps && !NILP (prefix))
1019                 wv->name++;
1020               wv->value = 0;
1021               wv->enabled = 1;
1022               wv->button_type = BUTTON_TYPE_NONE;
1023               wv->help = Qnil;
1024               save_wv = wv;
1025               prev_wv = 0;
1026             }
1027           else if (first_pane)
1028             {
1029               save_wv = wv;
1030               prev_wv = 0;
1031             }
1032           first_pane = 0;
1033           i += MENU_ITEMS_PANE_LENGTH;
1034         }
1035       else
1036         {
1037           /* Create a new item within current pane.  */
1038           Lisp_Object item_name, enable, descrip, def, type, selected, help;
1039           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
1040           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
1041           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
1042           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
1043           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
1044           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
1045           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
1047 #ifndef HAVE_MULTILINGUAL_MENU
1048           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
1049             {
1050               item_name = ENCODE_MENU_STRING (item_name);
1051               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
1052             }
1054           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
1055             {
1056               descrip = ENCODE_MENU_STRING (descrip);
1057               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
1058             }
1059 #endif /* not HAVE_MULTILINGUAL_MENU */
1061           wv = xmalloc_widget_value ();
1062           if (prev_wv)
1063             prev_wv->next = wv;
1064           else
1065             save_wv->contents = wv;
1066           wv->name = (char *) SDATA (item_name);
1067           if (!NILP (descrip))
1068             wv->key = (char *) SDATA (descrip);
1069           wv->value = 0;
1070           /* If this item has a null value,
1071              make the call_data null so that it won't display a box
1072              when the mouse is on it.  */
1073           wv->call_data
1074               = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
1075           wv->enabled = !NILP (enable);
1077           if (NILP (type))
1078             wv->button_type = BUTTON_TYPE_NONE;
1079           else if (EQ (type, QCtoggle))
1080             wv->button_type = BUTTON_TYPE_TOGGLE;
1081           else if (EQ (type, QCradio))
1082             wv->button_type = BUTTON_TYPE_RADIO;
1083           else
1084             abort ();
1086           wv->selected = !NILP (selected);
1088           if (! STRINGP (help))
1089             help = Qnil;
1091           wv->help = help;
1093           prev_wv = wv;
1095           i += MENU_ITEMS_ITEM_LENGTH;
1096         }
1097     }
1098   }
1099 #endif
1101   if (!NILP (title))
1102     {
1103       widget_value *wv_title = xmalloc_widget_value ();
1104       widget_value *wv_sep = xmalloc_widget_value ();
1106       /* Maybe replace this separator with a bitmap or owner-draw item
1107          so that it looks better.  Having two separators looks odd.  */
1108       wv_sep->name = "--";
1109       wv_sep->next = first_wv->contents;
1110       wv_sep->help = Qnil;
1112 #ifndef HAVE_MULTILINGUAL_MENU
1113       if (STRING_MULTIBYTE (title))
1114         title = ENCODE_MENU_STRING (title);
1115 #endif
1117       wv_title->name = (char *) SDATA (title);
1118       wv_title->enabled = NO;
1119       wv_title->button_type = BUTTON_TYPE_NONE;
1120       wv_title->help = Qnil;
1121       wv_title->next = wv_sep;
1122       first_wv->contents = wv_title;
1123     }
1125   pmenu = [[EmacsMenu alloc] initWithTitle:
1126                                [NSString stringWithUTF8String: SDATA (title)]];
1127   [pmenu fillWithWidgetValue: first_wv->contents];
1128   free_menubar_widget_value_tree (first_wv);
1129   unbind_to (specpdl_count2, Qnil);
1131   popup_activated_flag = 1;
1132   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1133   popup_activated_flag = 0;
1134   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1136   UNBLOCK_INPUT;
1137   discard_menu_items ();
1138   unbind_to (specpdl_count, Qnil);
1139   UNGCPRO;
1141   if (error_name) error (error_name);
1142   return tem;
1148 /* ==========================================================================
1150     Toolbar: externally-called functions
1152    ========================================================================== */
1154 void
1155 free_frame_tool_bar (FRAME_PTR f)
1156 /* --------------------------------------------------------------------------
1157     Under NS we just hide the toolbar until it might be needed again.
1158    -------------------------------------------------------------------------- */
1160   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1163 void
1164 update_frame_tool_bar (FRAME_PTR f)
1165 /* --------------------------------------------------------------------------
1166     Update toolbar contents
1167    -------------------------------------------------------------------------- */
1169   int i;
1170   EmacsToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar];
1172   [toolbar clearActive];
1174   /* update EmacsToolbar as in GtkUtils, build items list */
1175   for (i = 0; i < f->n_tool_bar_items; ++i)
1176     {
1177 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1178                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1180       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1181       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1182       int idx;
1183       int img_id;
1184       struct image *img;
1185       Lisp_Object image;
1186       Lisp_Object helpObj;
1187       char *helpText;
1189       /* If image is a vector, choose the image according to the
1190          button state.  */
1191       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1192       if (VECTORP (image))
1193         {
1194           /* NS toolbar auto-computes disabled and selected images */
1195           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1196           xassert (ASIZE (image) >= idx);
1197           image = AREF (image, idx);
1198         }
1199       else
1200         {
1201           idx = -1;
1202         }
1203       /* Ignore invalid image specifications.  */
1204       if (!valid_image_p (image))
1205         {
1206           NSLog (@"Invalid image for toolbar item");
1207           continue;
1208         }
1210       img_id = lookup_image (f, image);
1211       img = IMAGE_FROM_ID (f, img_id);
1212       prepare_image_for_display (f, img);
1214       if (img->load_failed_p || img->pixmap == nil)
1215         {
1216           NSLog (@"Could not prepare toolbar image for display.");
1217           continue;
1218         }
1220       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1221       if (NILP (helpObj))
1222         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1223       helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1225       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1226                                enabled: enabled_p];
1227 #undef TOOLPROP
1228     }
1230   if (![toolbar isVisible])
1231       [toolbar setVisible: YES];
1233   if ([toolbar changed])
1234     {
1235       /* inform app that toolbar has changed */
1236       NSDictionary *dict = [toolbar configurationDictionary];
1237       NSMutableDictionary *newDict = [dict mutableCopy];
1238       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1239       NSObject *key;
1240       while ((key = [keys nextObject]) != nil)
1241         {
1242           NSObject *val = [dict objectForKey: key];
1243           if ([val isKindOfClass: [NSArray class]])
1244             {
1245               [newDict setObject:
1246                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1247                           forKey: key];
1248               break;
1249             }
1250         }
1251       [toolbar setConfigurationFromDictionary: newDict];
1252       [newDict release];
1253     }
1258 /* ==========================================================================
1260     Toolbar: class implementation
1262    ========================================================================== */
1264 @implementation EmacsToolbar
1266 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1268   self = [super initWithIdentifier: identifier];
1269   emacsView = view;
1270   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1271   [self setSizeMode: NSToolbarSizeModeSmall];
1272   [self setDelegate: self];
1273   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1274   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1275   prevEnablement = enablement = 0L;
1276   return self;
1279 - (void)dealloc
1281   [prevIdentifiers release];
1282   [activeIdentifiers release];
1283   [identifierToItem release];
1284   [super dealloc];
1287 - (void) clearActive
1289   [prevIdentifiers release];
1290   prevIdentifiers = [activeIdentifiers copy];
1291   [activeIdentifiers removeAllObjects];
1292   prevEnablement = enablement;
1293   enablement = 0L;
1296 - (BOOL) changed
1298   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1299     enablement == prevEnablement ? NO : YES;
1302 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1303                         helpText: (char *)help enabled: (BOOL)enabled
1305   /* 1) come up w/identifier */
1306   NSString *identifier
1307       = [NSString stringWithFormat: @"%u", [img hash]];
1309   /* 2) create / reuse item */
1310   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1311   if (item == nil)
1312     {
1313       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1314                autorelease];
1315       [item setImage: img];
1316       [item setToolTip: [NSString stringWithCString: help]];
1317       [item setTarget: emacsView];
1318       [item setAction: @selector (toolbarClicked:)];
1319     }
1321   [item setTag: idx];
1322   [item setEnabled: enabled];
1324   /* 3) update state */
1325   [identifierToItem setObject: item forKey: identifier];
1326   [activeIdentifiers addObject: identifier];
1327   enablement = (enablement << 1) | (enabled == YES);
1330 /* This overrides super's implementation, which automatically sets
1331    all items to enabled state (for some reason). */
1332 - (void)validateVisibleItems { }
1335 /* delegate methods */
1337 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1338       itemForItemIdentifier: (NSString *)itemIdentifier
1339   willBeInsertedIntoToolbar: (BOOL)flag
1341   /* look up NSToolbarItem by identifier and return... */
1342   return [identifierToItem objectForKey: itemIdentifier];
1345 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1347   /* return entire set.. */
1348   return activeIdentifiers;
1351 /* for configuration palette (not yet supported) */
1352 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1354   /* return entire set... */
1355   return [identifierToItem allKeys];
1358 /* optional and unneeded */
1359 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1360 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1361 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1363 @end  /* EmacsToolbar */
1367 /* ==========================================================================
1369     Tooltip: class implementation
1371    ========================================================================== */
1373 /* Needed because NeXTstep does not provide enough control over tooltip
1374    display. */
1375 @implementation EmacsTooltip
1377 - init
1379   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1380                                             blue: 0.792 alpha: 0.95];
1381   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1382   NSFont *sfont = [font screenFont];
1383   int height = [sfont ascender] - [sfont descender];
1384 /*[font boundingRectForFont].size.height; */
1385   NSRect r = NSMakeRect (0, 0, 100, height+6);
1387   textField = [[NSTextField alloc] initWithFrame: r];
1388   [textField setFont: font];
1389   [textField setBackgroundColor: col];
1391   [textField setEditable: NO];
1392   [textField setSelectable: NO];
1393   [textField setBordered: YES];
1394   [textField setBezeled: YES];
1395   [textField setDrawsBackground: YES];
1397   win = [[NSWindow alloc]
1398             initWithContentRect: [textField frame]
1399                       styleMask: 0
1400                         backing: NSBackingStoreBuffered
1401                           defer: YES];
1402   [win setReleasedWhenClosed: NO];
1403   [win setDelegate: self];
1404   [[win contentView] addSubview: textField];
1405 /*  [win setBackgroundColor: col]; */
1406   [win setOpaque: NO];
1408   return self;
1411 - (void) dealloc
1413   [win close];
1414   [win release];
1415   [textField release];
1416   [super dealloc];
1419 - (void) setText: (char *)text
1421   NSString *str = [NSString stringWithUTF8String: text];
1422   NSRect r = [textField frame];
1423   r.size.width = [[[textField font] screenFont] widthOfString: str] + 8;
1424   [textField setFrame: r];
1425   [textField setStringValue: str];
1428 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1430   NSRect wr = [win frame];
1432   wr.origin = NSMakePoint (x, y);
1433   wr.size = [textField frame].size;
1435   [win setFrame: wr display: YES];
1436   [win orderFront: self];
1437   [win display];
1438   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1439                                          selector: @selector (hide)
1440                                          userInfo: nil repeats: NO];
1441   [timer retain];
1444 - (void) hide
1446   [win close];
1447   if (timer != nil)
1448     {
1449       if ([timer isValid])
1450         [timer invalidate];
1451       [timer release];
1452       timer = nil;
1453     }
1456 - (BOOL) isActive
1458   return timer != nil;
1461 - (NSRect) frame
1463   return [textField frame];
1466 @end  /* EmacsTooltip */
1470 /* ==========================================================================
1472     Popup Dialog: implementing functions
1474    ========================================================================== */
1476 Lisp_Object
1477 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1479   id dialog;
1480   Lisp_Object window, tem;
1481   struct frame *f;
1482   NSPoint p;
1483   BOOL isQ;
1485   NSTRACE (x-popup-dialog);
1486   
1487   check_ns ();
1489   isQ = NILP (header);
1491   if (EQ (position, Qt))
1492     {
1493       window = selected_window;
1494     }
1495   else if (CONSP (position))
1496     {
1497       Lisp_Object tem;
1498       tem = Fcar (position);
1499       if (XTYPE (tem) == Lisp_Cons)
1500         window = Fcar (Fcdr (position));
1501       else
1502         {
1503           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1504           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1505         }
1506     }
1507   else if (FRAMEP (position))
1508     {
1509       window = position;
1510     }
1511   else
1512     {
1513       CHECK_LIVE_WINDOW (position);
1514       window = position;
1515     }
1516   
1517   if (FRAMEP (window))
1518     f = XFRAME (window);
1519   else
1520     {
1521       CHECK_LIVE_WINDOW (window);
1522       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1523     }
1524   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1525   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1526   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1527                                            isQuestion: isQ];
1528   popup_activated_flag = 1;
1529   tem = [dialog runDialogAt: p];
1530   popup_activated_flag = 0;
1532   [dialog close];
1534   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1535   return tem;
1539 /* ==========================================================================
1541     Popup Dialog: class implementation
1543    ========================================================================== */
1545 @interface FlippedView : NSView
1548 @end
1550 @implementation FlippedView
1551 - (BOOL)isFlipped
1553   return YES;
1555 @end
1557 @implementation EmacsDialogPanel
1559 #define SPACER          8.0
1560 #define ICONSIZE        64.0
1561 #define TEXTHEIGHT      20.0
1562 #define MINCELLWIDTH    90.0
1564 - initWithContentRect: (NSRect)contentRect styleMask: (unsigned int)aStyle
1565               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1567   NSSize spacing = {SPACER, SPACER};
1568   NSRect area;
1569   char this_cmd_name[80];
1570   id cell;
1571   static NSImageView *imgView;
1572   static FlippedView *contentView;
1574   if (imgView == nil)
1575     {
1576       NSImage *img;
1577       area.origin.x   = 3*SPACER;
1578       area.origin.y   = 2*SPACER;
1579       area.size.width = ICONSIZE;
1580       area.size.height= ICONSIZE;
1581       img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1582       [img setScalesWhenResized: YES];
1583       [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1584       imgView = [[NSImageView alloc] initWithFrame: area];
1585       [imgView setImage: img];
1586       [imgView setEditable: NO];
1587       [img release];
1588     }
1590   aStyle = NSTitledWindowMask;
1591   flag = YES;
1592   rows = 0;
1593   cols = 1;
1594   [super initWithContentRect: contentRect styleMask: aStyle
1595                      backing: backingType defer: flag];
1596   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1597   [self setContentView: contentView];
1599   [[self contentView] setAutoresizesSubviews: YES];
1601   [[self contentView] addSubview: imgView];
1602   [self setTitle: @""];
1604   area.origin.x   += ICONSIZE+2*SPACER;
1605 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1606   area.size.width = 400;
1607   area.size.height= TEXTHEIGHT;
1608   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1609   [[self contentView] addSubview: command];
1610   [command setStringValue: @"Emacs"];
1611   [command setDrawsBackground: NO];
1612   [command setBezeled: NO];
1613   [command setSelectable: NO];
1614   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1616 /*  area.origin.x   = ICONSIZE+2*SPACER;
1617   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1618   area.size.width = 400;
1619   area.size.height= 2;
1620   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1621   [[self contentView] addSubview: tem];
1622   [tem setTitlePosition: NSNoTitle];
1623   [tem setAutoresizingMask: NSViewWidthSizable];*/
1625 /*  area.origin.x = ICONSIZE+2*SPACER; */
1626   area.origin.y += TEXTHEIGHT+SPACER;
1627   area.size.width = 400;
1628   area.size.height= TEXTHEIGHT;
1629   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1630   [[self contentView] addSubview: title];
1631   [title setDrawsBackground: NO];
1632   [title setBezeled: NO];
1633   [title setSelectable: NO];
1634   [title setFont: [NSFont systemFontOfSize: 11.0]];
1636   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1637   [cell setBordered: NO];
1638   [cell setEnabled: NO];
1639   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1640   [cell setBezelStyle: NSRoundedBezelStyle];
1642   matrix = [[NSMatrix alloc] initWithFrame: contentRect 
1643                                       mode: NSHighlightModeMatrix 
1644                                  prototype: cell 
1645                               numberOfRows: 0 
1646                            numberOfColumns: 1];
1647   [[self contentView] addSubview: matrix];
1648   [matrix release];
1649   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1650                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1651   [matrix setIntercellSpacing: spacing];
1653   [self setOneShot: YES];
1654   [self setReleasedWhenClosed: YES];
1655   [self setHidesOnDeactivate: YES];
1656   return self;
1660 - (BOOL)windowShouldClose: (id)sender
1662   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1663   return NO;
1667 void process_dialog (id window, Lisp_Object list)
1669   Lisp_Object item;
1670   int row = 0;
1672   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1673     {
1674       item = XCAR (list);
1675       if (XTYPE (item) == Lisp_String)
1676         {
1677           [window addString: XSTRING (item)->data row: row++];
1678         }
1679       else if (XTYPE (item) == Lisp_Cons)
1680         {
1681           [window addButton: XSTRING (XCAR (item))->data
1682                       value: XCDR (item) row: row++];
1683         }
1684       else if (NILP (item))
1685         {
1686           [window addSplit];
1687           row = 0;
1688         }
1689     }
1693 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1695   id cell;
1696        
1697   if (row >= rows)
1698     {
1699       [matrix addRow];
1700       rows++;
1701     }
1702   cell = [matrix cellAtRow: row column: cols-1];
1703   [cell setTarget: self];
1704   [cell setAction: @selector (clicked: )];
1705   [cell setTitle: [NSString stringWithUTF8String: str]];
1706   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1707   [cell setBordered: YES];
1708   [cell setEnabled: YES];
1710   return self;
1714 - addString: (char *)str row: (int)row
1716   id cell;
1717        
1718   if (row >= rows)
1719     {
1720       [matrix addRow];
1721       rows++;
1722     }
1723   cell = [matrix cellAtRow: row column: cols-1];
1724   [cell setTitle: [NSString stringWithUTF8String: str]];
1725   [cell setBordered: YES];
1726   [cell setEnabled: NO];
1728   return self;
1732 - addSplit
1734   [matrix addColumn];
1735   cols++;
1736   return self;
1740 - clicked: sender
1742   NSArray *sellist = nil;
1743   EMACS_INT seltag;
1745   sellist = [sender selectedCells];
1746   if ([sellist count]<1) 
1747     return self;
1749   seltag = [[sellist objectAtIndex: 0] tag];
1750   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1751     [NSApp stopModalWithCode: seltag];
1752   return self;
1756 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1758   Lisp_Object head;
1759   [super init];
1761   if (XTYPE (contents) == Lisp_Cons)
1762     {
1763       head = Fcar (contents);
1764       process_dialog (self, Fcdr (contents));
1765     }
1766   else
1767     head = contents;
1769   if (XTYPE (head) == Lisp_String)
1770       [title setStringValue:
1771                  [NSString stringWithUTF8String: XSTRING (head)->data]];
1772   else if (isQ == YES)
1773       [title setStringValue: @"Question"];
1774   else
1775       [title setStringValue: @"Information"];
1777   {
1778     int i;
1779     NSRect r, s, t;
1781     if (cols == 1 && rows > 1)  /* Never told where to split */
1782       {
1783         [matrix addColumn];
1784         for (i = 0; i<rows/2; i++)
1785           {
1786             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1787                       atRow: i column: 1];
1788             [matrix removeRow: (rows+1)/2];
1789           }
1790       }
1792     [matrix sizeToFit];
1793     {
1794       NSSize csize = [matrix cellSize];
1795       if (csize.width < MINCELLWIDTH)
1796         {
1797           csize.width = MINCELLWIDTH;
1798           [matrix setCellSize: csize];
1799           [matrix sizeToCells];
1800         }
1801     }
1803     [title sizeToFit];
1804     [command sizeToFit];
1806     t = [matrix frame];
1807     r = [title frame];
1808     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1809       {
1810         t.origin.x   = r.origin.x;
1811         t.size.width = r.size.width;
1812       }
1813     r = [command frame];
1814     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1815       {
1816         t.origin.x   = r.origin.x;
1817         t.size.width = r.size.width;
1818       }
1820     r = [self frame];
1821     s = [(NSView *)[self contentView] frame];
1822     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1823     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1824     [self setFrame: r display: NO];
1825   }
1827   return self;
1831 - (void)dealloc
1833   { [super dealloc]; return; };
1837 - (Lisp_Object)runDialogAt: (NSPoint)p
1839   NSEvent *e;
1840   NSModalSession session;
1841   int ret;
1843   [self center];  /*XXX p ignored? */
1844   [self orderFront: NSApp];
1846   session = [NSApp beginModalSessionForWindow: self];
1847   while ((ret = [NSApp runModalSession: session]) == NSRunContinuesResponse)
1848     {
1849     (e = [NSApp nextEventMatchingMask: NSAnyEventMask
1850                             untilDate: [NSDate distantFuture]
1851                                inMode: NSEventTrackingRunLoopMode
1852                               dequeue: NO]);
1853 /*fprintf (stderr, "ret = %d\te = %p\n", ret, e); */
1854     }
1855   [NSApp endModalSession: session];
1857   {                             // FIXME: BIG UGLY HACK!!!
1858       Lisp_Object tmp;
1859       *(EMACS_INT*)(&tmp) = ret;
1860       return tmp;
1861   }
1864 @end
1868 /* ==========================================================================
1870     Lisp definitions
1872    ========================================================================== */
1874 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1875        doc: /* Cause the NS menu to be re-calculated.  */)
1876      ()
1878   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1879   return Qnil;
1883 DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
1884        doc: /* Pop up a deck-of-cards menu and return user's selection.
1885 POSITION is a position specification.  This is either a mouse button event
1886 or a list ((XOFFSET YOFFSET) WINDOW)
1887 where XOFFSET and YOFFSET are positions in pixels from the top left
1888 corner of WINDOW.  (WINDOW may be a window or a frame object.)
1889 This controls the position of the top left of the menu as a whole.
1890 If POSITION is t, it means to use the current mouse position.
1892 MENU is a specifier for a menu.  For the simplest case, MENU is a keymap.
1893 The menu items come from key bindings that have a menu string as well as
1894 a definition; actually, the \"definition\" in such a key binding looks like
1895 \(STRING . REAL-DEFINITION).  To give the menu a title, put a string into
1896 the keymap as a top-level element.
1898 If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.
1899 Otherwise, REAL-DEFINITION should be a valid key binding definition.
1901 You can also use a list of keymaps as MENU.
1902   Then each keymap makes a separate pane.
1904 When MENU is a keymap or a list of keymaps, the return value is the
1905 list of events corresponding to the user's choice. Note that
1906 `x-popup-menu' does not actually execute the command bound to that
1907 sequence of events.
1909 Alternatively, you can specify a menu of multiple panes
1910   with a list of the form (TITLE PANE1 PANE2...),
1911 where each pane is a list of form (TITLE ITEM1 ITEM2...).
1912 Each ITEM is normally a cons cell (STRING . VALUE);
1913 but a string can appear as an item--that makes a nonselectable line
1914 in the menu.
1915 With this form of menu, the return value is VALUE from the chosen item.  */)
1916      (position, menu)
1917      Lisp_Object position, menu;
1919   return ns_popup_menu (position, menu);
1923 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1924        doc: /* Pop up a dialog box and return user's selection.
1925 POSITION specifies which frame to use.
1926 This is normally a mouse button event or a window or frame.
1927 If POSITION is t, it means to use the frame the mouse is on.
1928 The dialog box appears in the middle of the specified frame.
1930 CONTENTS specifies the alternatives to display in the dialog box.
1931 It is a list of the form (DIALOG ITEM1 ITEM2...).
1932 Each ITEM is a cons cell (STRING . VALUE).
1933 The return value is VALUE from the chosen item.
1935 An ITEM may also be just a string--that makes a nonselectable item.
1936 An ITEM may also be nil--that means to put all preceding items
1937 on the left of the dialog box and all following items on the right.
1938 \(By default, approximately half appear on each side.)
1940 If HEADER is non-nil, the frame title for the box is "Information",
1941 otherwise it is "Question".
1943 If the user gets rid of the dialog box without making a valid choice,
1944 for instance using the window manager, then this produces a quit and
1945 `x-popup-dialog' does not return.  */)
1946      (position, contents, header)
1947      Lisp_Object position, contents, header;
1949   return ns_popup_dialog (position, contents, header);
1952 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1953        doc: /* Return t if a menu or popup dialog is active.  */)
1954      ()
1956   return popup_activated () ? Qt : Qnil;
1959 /* ==========================================================================
1961     Lisp interface declaration
1963    ========================================================================== */
1965 void
1966 syms_of_nsmenu ()
1968   defsubr (&Sx_popup_menu);
1969   defsubr (&Sx_popup_dialog);
1970   defsubr (&Sns_reset_menu);
1971   defsubr (&Smenu_or_popup_active_p);
1972   staticpro (&menu_items);
1973   menu_items = Qnil;
1975   Qdebug_on_next_call = intern ("debug-on-next-call");
1976   staticpro (&Qdebug_on_next_call);
1979 // arch-tag: 75773656-52e5-4c44-a398-47bd87b32619