* files.el (safe-local-eval-forms): Allow time-stamp in
[emacs.git] / src / nsmenu.m
blob749a1d0fe76be319314868a63472e070ac5f5676
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007, 2008, 2009 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"
28 #include "lisp.h"
29 #include "window.h"
30 #include "buffer.h"
31 #include "keymap.h"
32 #include "coding.h"
33 #include "commands.h"
34 #include "blockinput.h"
35 #include "nsterm.h"
36 #include "termhooks.h"
37 #include "keyboard.h"
39 #define NSMENUPROFILE 0
41 #if NSMENUPROFILE
42 #include <sys/timeb.h>
43 #include <sys/types.h>
44 #endif
46 #define MenuStagger 10.0
48 #if 0
49 int menu_trace_num = 0;
50 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
51                                 __FILE__, __LINE__, ++menu_trace_num)
52 #else
53 #define NSTRACE(x)
54 #endif
56 #if 0
57 /* Include lisp -> C common menu parsing code */
58 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
59 #include "nsmenu_common.c"
60 #endif
62 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
63 extern Lisp_Object QCtoggle, QCradio;
65 extern Lisp_Object Vmenu_updating_frame;
67 Lisp_Object Qdebug_on_next_call;
68 extern Lisp_Object Voverriding_local_map, Voverriding_local_map_menu_flag,
69                    Qoverriding_local_map, Qoverriding_terminal_local_map;
71 extern long context_menu_value;
72 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
74 /* Nonzero means a menu is currently active.  */
75 static int popup_activated_flag;
76 static NSModalSession popupSession;
78 /* NOTE: toolbar implementation is at end,
79   following complete menu implementation. */
82 /* ==========================================================================
84     Menu: Externally-called functions
86    ========================================================================== */
89 /* FIXME: not currently used, but should normalize with other terms. */
90 void
91 x_activate_menubar (struct frame *f)
93     fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
97 /* Supposed to discard menubar and free storage.  Since we share the
98    menubar among frames and update its context for the focused window,
99    there is nothing to do here. */
100 void
101 free_frame_menubar (struct frame *f)
103   return;
108 popup_activated ()
110   return popup_activated_flag;
114 /* --------------------------------------------------------------------------
115     Update menubar.  Three cases:
116     1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
117        just top-level menu strings (OS X), or goto case (2) (GNUstep).
118     2) deep_p = 1, submenu = nil: Recompute all submenus.
119     3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
120    -------------------------------------------------------------------------- */
121 void
122 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
124   NSAutoreleasePool *pool;
125   id menu = [NSApp mainMenu];
126   static EmacsMenu *last_submenu = nil;
127   BOOL needsSet = NO;
128   const char *submenuTitle = [[submenu title] UTF8String];
129   extern int waiting_for_input;
130   int owfi;
131   Lisp_Object items;
132   widget_value *wv, *first_wv, *prev_wv = 0;
133   int i;
135 #if NSMENUPROFILE
136   struct timeb tb;
137   long t;
138 #endif
140   NSTRACE (set_frame_menubar);
142   if (f != SELECTED_FRAME ())
143       return;
144   XSETFRAME (Vmenu_updating_frame, f);
145 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
147   BLOCK_INPUT;
148   pool = [[NSAutoreleasePool alloc] init];
150   /* Menu may have been created automatically; if so, discard it. */
151   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
152     {
153       [menu release];
154       menu = nil;
155     }
157   if (menu == nil)
158     {
159       menu = [[EmacsMenu alloc] initWithTitle: @"Emacs"];
160       needsSet = YES;
161     }
162   else
163     {  /* close up anything on there */
164       id attMenu = [menu attachedMenu];
165       if (attMenu != nil)
166         [attMenu close];
167     }
169 #if NSMENUPROFILE
170   ftime (&tb);
171   t = -(1000*tb.time+tb.millitm);
172 #endif
174   /* widget_value is a straightforward object translation of emacs's
175      Byzantine lisp menu structures */
176   wv = xmalloc_widget_value ();
177   wv->name = "Emacs";
178   wv->value = 0;
179   wv->enabled = 1;
180   wv->button_type = BUTTON_TYPE_NONE;
181   wv->help = Qnil;
182   first_wv = wv;
184 #ifdef NS_IMPL_GNUSTEP
185   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
186 #endif
188   if (deep_p)
189     {
190       /* Fully parse one or more of the submenus. */
191       int n = 0;
192       int *submenu_start, *submenu_end;
193       int *submenu_top_level_items, *submenu_n_panes;
194       struct buffer *prev = current_buffer;
195       Lisp_Object buffer;
196       int specpdl_count = SPECPDL_INDEX ();
197       int previous_menu_items_used = f->menu_bar_items_used;
198       Lisp_Object *previous_items
199         = (Lisp_Object *) alloca (previous_menu_items_used
200                                   * sizeof (Lisp_Object));
202       /* lisp preliminaries */
203       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
204       specbind (Qinhibit_quit, Qt);
205       specbind (Qdebug_on_next_call, Qnil);
206       record_unwind_save_match_data ();
207       if (NILP (Voverriding_local_map_menu_flag))
208         {
209           specbind (Qoverriding_terminal_local_map, Qnil);
210           specbind (Qoverriding_local_map, Qnil);
211         }
212       set_buffer_internal_1 (XBUFFER (buffer));
214       /* TODO: for some reason this is not needed in other terms,
215            but some menu updates call Info-extract-pointer which causes
216            abort-on-error if waiting-for-input.  Needs further investigation. */
217       owfi = waiting_for_input;
218       waiting_for_input = 0;
220       /* lucid hook and possible reset */
221       safe_run_hooks (Qactivate_menubar_hook);
222       if (! NILP (Vlucid_menu_bar_dirty_flag))
223         call0 (Qrecompute_lucid_menubar);
224       safe_run_hooks (Qmenu_bar_update_hook);
225       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
227       /* Now ready to go */
228       items = FRAME_MENU_BAR_ITEMS (f);
230       /* Save the frame's previous menu bar contents data */
231       if (previous_menu_items_used)
232         bcopy (XVECTOR (f->menu_bar_vector)->contents, previous_items,
233                previous_menu_items_used * sizeof (Lisp_Object));
235       /* parse stage 1: extract from lisp */
236       save_menu_items ();
238       menu_items = f->menu_bar_vector;
239       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
240       submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
241       submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
242       submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
243       submenu_top_level_items
244         = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
245       init_menu_items ();
246       for (i = 0; i < XVECTOR (items)->size; i += 4)
247         {
248           Lisp_Object key, string, maps;
250           key = XVECTOR (items)->contents[i];
251           string = XVECTOR (items)->contents[i + 1];
252           maps = XVECTOR (items)->contents[i + 2];
253           if (NILP (string))
254             break;
256           /* FIXME: we'd like to only parse the needed submenu, but this
257                was causing crashes in the _common parsing code.. need to make
258                sure proper initialization done.. */
259 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
260              continue; */
262           submenu_start[i] = menu_items_used;
264           menu_items_n_panes = 0;
265           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
266           submenu_n_panes[i] = menu_items_n_panes;
267           submenu_end[i] = menu_items_used;
268           n++;
269         }
271       finish_menu_items ();
272       waiting_for_input = owfi;
275       if (submenu && n == 0)
276         {
277           /* should have found a menu for this one but didn't */
278           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
279                   submenuTitle);
280           discard_menu_items ();
281           unbind_to (specpdl_count, Qnil);
282           [pool release];
283           UNBLOCK_INPUT;
284           return;
285         }
287       /* parse stage 2: insert into lucid 'widget_value' structures
288          [comments in other terms say not to evaluate lisp code here] */
289       wv = xmalloc_widget_value ();
290       wv->name = "menubar";
291       wv->value = 0;
292       wv->enabled = 1;
293       wv->button_type = BUTTON_TYPE_NONE;
294       wv->help = Qnil;
295       first_wv = wv;
297       for (i = 0; i < 4*n; i += 4)
298         {
299           menu_items_n_panes = submenu_n_panes[i];
300           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
301                                       submenu_top_level_items[i]);
302           if (prev_wv)
303             prev_wv->next = wv;
304           else
305             first_wv->contents = wv;
306           /* Don't set wv->name here; GC during the loop might relocate it.  */
307           wv->enabled = 1;
308           wv->button_type = BUTTON_TYPE_NONE;
309           prev_wv = wv;
310         }
312       set_buffer_internal_1 (prev);
314       /* Compare the new menu items with previous, and leave off if no change */
315       /* FIXME: following other terms here, but seems like this should be
316            done before parse stage 2 above, since its results aren't used */
317       if (previous_menu_items_used
318           && (!submenu || (submenu && submenu == last_submenu))
319           && menu_items_used == previous_menu_items_used)
320         {
321           for (i = 0; i < previous_menu_items_used; i++)
322             /* FIXME: this ALWAYS fails on Buffers menu items.. something
323                  about their strings causes them to change every time, so we
324                  double-check failures */
325             if (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i]))
326               if (!(STRINGP (previous_items[i])
327                     && STRINGP (XVECTOR (menu_items)->contents[i])
328                     && !strcmp (SDATA (previous_items[i]),
329                                SDATA (XVECTOR (menu_items)->contents[i]))))
330                   break;
331           if (i == previous_menu_items_used)
332             {
333               /* No change.. */
335 #if NSMENUPROFILE
336               ftime (&tb);
337               t += 1000*tb.time+tb.millitm;
338               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
339 #endif
341               free_menubar_widget_value_tree (first_wv);
342               discard_menu_items ();
343               unbind_to (specpdl_count, Qnil);
344               [pool release];
345               UNBLOCK_INPUT;
346               return;
347             }
348         }
349       /* The menu items are different, so store them in the frame */
350       /* FIXME: this is not correct for single-submenu case */
351       f->menu_bar_vector = menu_items;
352       f->menu_bar_items_used = menu_items_used;
354       /* Calls restore_menu_items, etc., as they were outside */
355       unbind_to (specpdl_count, Qnil);
357       /* Parse stage 2a: now GC cannot happen during the lifetime of the
358          widget_value, so it's safe to store data from a Lisp_String */
359       wv = first_wv->contents;
360       for (i = 0; i < XVECTOR (items)->size; i += 4)
361         {
362           Lisp_Object string;
363           string = XVECTOR (items)->contents[i + 1];
364           if (NILP (string))
365             break;
366 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
367                continue; */
369           wv->name = (char *) SDATA (string);
370           update_submenu_strings (wv->contents);
371           wv = wv->next;
372         }
374       /* Now, update the NS menu; if we have a submenu, use that, otherwise
375          create a new menu for each sub and fill it. */
376       if (submenu)
377         {
378           for (wv = first_wv->contents; wv; wv = wv->next)
379             {
380               if (!strcmp (submenuTitle, wv->name))
381                 {
382                   [submenu fillWithWidgetValue: wv->contents];
383                   last_submenu = submenu;
384                   break;
385                 }
386             }
387         }
388       else
389         {
390           [menu fillWithWidgetValue: first_wv->contents];
391         }
393     }
394   else
395     {
396       static int n_previous_strings = 0;
397       static char previous_strings[100][10];
398       static struct frame *last_f = NULL;
399       int n;
400       Lisp_Object string;
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           [pool release];
407           UNBLOCK_INPUT;
408           return;
409         }
412       /* check if no change.. this mechanism is a bit rough, but ready */
413       n = XVECTOR (items)->size / 4;
414       if (f == last_f && n_previous_strings == n)
415         {
416           for (i = 0; i<n; i++)
417             {
418               string = AREF (items, 4*i+1);
420               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
421                 continue;
422               if (NILP (string))
423                 if (previous_strings[i][0])
424                   break;
425               else
426                 continue;
427               if (strncmp (previous_strings[i], SDATA (string), 10))
428                 break;
429             }
431           if (i == n)
432             {
433               [pool release];
434               UNBLOCK_INPUT;
435               return;
436             }
437         }
439       [menu clear];
440       for (i = 0; i < XVECTOR (items)->size; i += 4)
441         {
442           string = XVECTOR (items)->contents[i + 1];
443           if (NILP (string))
444             break;
446           if (n < 100)
447             strncpy (previous_strings[i/4], SDATA (string), 10);
449           wv = xmalloc_widget_value ();
450           wv->name = (char *) SDATA (string);
451           wv->value = 0;
452           wv->enabled = 1;
453           wv->button_type = BUTTON_TYPE_NONE;
454           wv->help = Qnil;
455           wv->call_data = (void *) (EMACS_INT) (-1);
457 #ifdef NS_IMPL_COCOA
458           /* we'll update the real copy under app menu when time comes */
459           if (!strcmp ("Services", wv->name))
460             {
461               /* but we need to make sure it will update on demand */
462               [svcsMenu setFrame: f];
463               [svcsMenu setDelegate: svcsMenu];
464             }
465           else
466 #endif
467           [menu addSubmenuWithTitle: wv->name forFrame: f];
469           if (prev_wv)
470             prev_wv->next = wv;
471           else
472             first_wv->contents = wv;
473           prev_wv = wv;
474         }
476       last_f = f;
477       if (n < 100)
478         n_previous_strings = n;
479       else
480         n_previous_strings = 0;
482     }
483   free_menubar_widget_value_tree (first_wv);
486 #if NSMENUPROFILE
487   ftime (&tb);
488   t += 1000*tb.time+tb.millitm;
489   fprintf (stderr, "Menu update took %ld msec.\n", t);
490 #endif
492   /* set main menu */
493   if (needsSet)
494     [NSApp setMainMenu: menu];
496   [pool release];
497   UNBLOCK_INPUT;
502 /* Main emacs core entry point for menubar menus: called to indicate that the
503    frame's menus have changed, and the *step representation should be updated
504    from Lisp. */
505 void
506 set_frame_menubar (struct frame *f, int first_time, int deep_p)
508   ns_update_menubar (f, deep_p, nil);
512 /* Utility (from macmenu.c): is this item a separator? */
513 static int
514 name_is_separator (name)
515      const char *name;
517   const char *start = name;
519   /* Check if name string consists of only dashes ('-').  */
520   while (*name == '-') name++;
521   /* Separators can also be of the form "--:TripleSuperMegaEtched"
522      or "--deep-shadow".  We don't implement them yet, se we just treat
523      them like normal separators.  */
524   return (*name == '\0' || start + 2 == name);
528 /* ==========================================================================
530     Menu: class implementation
532    ========================================================================== */
535 /* Menu that can define itself from Emacs "widget_value"s and will lazily
536    update itself when user clicked.  Based on Carbon/AppKit implementation
537    by Yamamoto Mitsuharu. */
538 @implementation EmacsMenu
540 /* override designated initializer */
541 - initWithTitle: (NSString *)title
543   if (self = [super initWithTitle: title])
544     [self setAutoenablesItems: NO];
545   return self;
549 /* used for top-level */
550 - initWithTitle: (NSString *)title frame: (struct frame *)f
552   [self initWithTitle: title];
553   frame = f;
554 #ifdef NS_IMPL_COCOA
555   [self setDelegate: self];
556 #endif
557   return self;
561 - (void)setFrame: (struct frame *)f
563   frame = f;
567 /* delegate method called when a submenu is being opened: run a 'deep' call
568    to set_frame_menubar */
569 - (void)menuNeedsUpdate: (NSMenu *)menu
571   NSEvent *event;
572   if (!FRAME_LIVE_P (frame))
573     return;
574   event = [[FRAME_NS_VIEW (frame) window] currentEvent];
575   /* HACK: Cocoa/Carbon will request update on every keystroke
576      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
577      since key equivalents are handled through emacs.
578      On Leopard, even keystroke events generate SystemDefined events, but
579      their subtype is 8. */
580   if ([event type] != NSSystemDefined || [event subtype] == 8
581       /* Also, don't try this if from an event picked up asynchronously,
582          as lots of lisp evaluation happens in ns_update_menubar. */
583       || handling_signal != 0)
584     return;
585 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
586   ns_update_menubar (frame, 1, self);
590 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
592   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
593       && FRAME_NS_VIEW (SELECTED_FRAME ()))
594     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
595   return YES;
599 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
600    into an accelerator string.  We are only able to display a single character
601    for an accelerator, together with an optional modifier combination.  (Under
602    Carbon more control was possible, but in Cocoa multi-char strings passed to
603    NSMenuItem get ignored.  For now we try to display a super-single letter
604    combo, and return the others as strings to be appended to the item title.
605    (This is signaled by setting keyEquivModMask to 0 for now.) */
606 -(NSString *)parseKeyEquiv: (char *)key
608   char *tpos = key;
609   keyEquivModMask = NSCommandKeyMask;
611   if (!key || !strlen (key))
612     return @"";
613   
614   while (*tpos == ' ' || *tpos == '(')
615     tpos++;
616   if ((*tpos == 's') && (*(tpos+1) == '-'))
617     {
618       return [NSString stringWithFormat: @"%c", tpos[2]];
619     }
620   keyEquivModMask = 0; /* signal */
621   return [NSString stringWithUTF8String: tpos];
625 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
627   NSMenuItem *item;
628   widget_value *wv = (widget_value *)wvptr;
630   if (name_is_separator (wv->name))
631     {
632       item = [NSMenuItem separatorItem];
633       [self addItem: item];
634     }
635   else
636     {
637       NSString *title, *keyEq;
638       title = [NSString stringWithUTF8String: wv->name];
639       if (title == nil)
640         title = @"< ? >";  /* (get out in the open so we know about it) */
642       keyEq = [self parseKeyEquiv: wv->key];
643 #ifdef NS_IMPL_COCOA
644       /* OS X just ignores modifier strings longer than one character */
645       if (keyEquivModMask == 0)
646         title = [title stringByAppendingFormat: @" (%@)", keyEq];
647 #endif
649       item = [self addItemWithTitle: (NSString *)title
650                              action: @selector (menuDown:)
651                       keyEquivalent: keyEq];
652       [item setKeyEquivalentModifierMask: keyEquivModMask];
654       [item setEnabled: wv->enabled];
656       /* Draw radio buttons and tickboxes */
657       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
658                            wv->button_type == BUTTON_TYPE_RADIO))
659         [item setState: NSOnState];
660       else
661         [item setState: NSOffState];
663       [item setTag: (int)wv->call_data];
664     }
666   return item;
670 /* convenience */
671 -(void)clear
673   int n;
674   
675   for (n = [self numberOfItems]-1; n >= 0; n--)
676     {
677       NSMenuItem *item = [self itemAtIndex: n];
678       NSString *title = [item title];
679       if (([title length] == 0  /* OSX 10.5 */
680            || [@"Emacs" isEqualToString: title]  /* from 10.6 on */
681            || [@"Apple" isEqualToString: title]) /* older */
682           && ![item isSeparatorItem])
683         continue;
684       [self removeItemAtIndex: n];
685     }
689 - (void)fillWithWidgetValue: (void *)wvptr
691   widget_value *wv = (widget_value *)wvptr;
693   /* clear existing contents */
694   [self setMenuChangedMessagesEnabled: NO];
695   [self clear];
697   /* add new contents */
698   for (; wv != NULL; wv = wv->next)
699     {
700       NSMenuItem *item = [self addItemWithWidgetValue: wv];
702       if (wv->contents)
703         {
704           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
706           [self setSubmenu: submenu forItem: item];
707           [submenu fillWithWidgetValue: wv->contents];
708           [submenu release];
709           [item setAction: nil];
710         }
711     }
713   [self setMenuChangedMessagesEnabled: YES];
714 #ifdef NS_IMPL_GNUSTEP
715   if ([[self window] isVisible])
716     [self sizeToFit];
717 #else
718   if ([self supermenu] == nil)
719     [self sizeToFit];
720 #endif
724 /* adds an empty submenu and returns it */
725 - (EmacsMenu *)addSubmenuWithTitle: (char *)title forFrame: (struct frame *)f
727   NSString *titleStr = [NSString stringWithUTF8String: title];
728   NSMenuItem *item = [self addItemWithTitle: titleStr
729                                      action: nil /*@selector (menuDown:) */
730                               keyEquivalent: @""];
731   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
732   [self setSubmenu: submenu forItem: item];
733   [submenu release];
734   return submenu;
737 /* run a menu in popup mode */
738 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
739                  keymaps: (int)keymaps
741   EmacsView *view = FRAME_NS_VIEW (f);
742 /*   p = [view convertPoint:p fromView: nil]; */
743   p.y = NSHeight ([view frame]) - p.y;
744   NSEvent *e = [[view window] currentEvent];
745   NSEvent *event = [NSEvent mouseEventWithType: NSRightMouseDown
746                                       location: p
747                                  modifierFlags: 0
748                                      timestamp: [e timestamp]
749                                   windowNumber: [[view window] windowNumber]
750                                        context: [e context]
751                                    eventNumber: 0/*[e eventNumber] */
752                                     clickCount: 1
753                                       pressure: 0];
754   long retVal;
756   context_menu_value = -1;
757   [NSMenu popUpContextMenu: self withEvent: event forView: view];
758   retVal = context_menu_value;
759   context_menu_value = 0;
760   return retVal > 0
761       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
762       : Qnil;
765 @end  /* EmacsMenu */
769 /* ==========================================================================
771     Context Menu: implementing functions
773    ========================================================================== */
775 static Lisp_Object
776 cleanup_popup_menu (Lisp_Object arg)
778   discard_menu_items ();
779   return Qnil;
783 static Lisp_Object
784 ns_popup_menu (Lisp_Object position, Lisp_Object menu)
786   EmacsMenu *pmenu;
787   struct frame *f = NULL;
788   NSPoint p;
789   Lisp_Object window, x, y, tem, keymap, title;
790   struct gcpro gcpro1;
791   int specpdl_count = SPECPDL_INDEX (), specpdl_count2;
792   char *error_name = NULL;
793   int keymaps = 0;
794   widget_value *wv, *first_wv = 0;
796   NSTRACE (ns_popup_menu);
798   if (!NILP (position))
799     {
800       check_ns ();
801   
802       if (EQ (position, Qt)
803           || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
804                                    || EQ (XCAR (position), Qtool_bar))))
805         {
806           /* Use the mouse's current position.  */
807           struct frame *new_f = SELECTED_FRAME ();
809           if (FRAME_TERMINAL (new_f)->mouse_position_hook)
810             (*FRAME_TERMINAL (new_f)->mouse_position_hook)
811               (&new_f, 0, 0, 0, &x, &y, 0);
812           if (new_f != 0)
813             XSETFRAME (window, new_f);
814           else
815             {
816               window = selected_window;
817               x = make_number (0);
818               y = make_number (0);
819             }
820         }
821       else
822         {
823           CHECK_CONS (position);
824           tem = Fcar (position);
825           if (XTYPE (tem) == Lisp_Cons)
826             {
827               window = Fcar (Fcdr (position));
828               x = Fcar (tem);
829               y = Fcar (Fcdr (tem));
830             }
831           else
832             {
833               tem = Fcar (Fcdr (position));
834               window = Fcar (tem);
835               tem = Fcar (Fcdr (Fcdr (tem)));
836               x = Fcar (tem);
837               y = Fcdr (tem);
838             }
839         }
840   
841       CHECK_NUMBER (x);
842       CHECK_NUMBER (y);
844       if (FRAMEP (window))
845         {
846           f = XFRAME (window);
847       
848           p.x = 0;
849           p.y = 0;
850         }
851       else
852         {
853           struct window *win = XWINDOW (window);
854           CHECK_LIVE_WINDOW (window);
855           f = XFRAME (WINDOW_FRAME (win));
856           p.x = FRAME_COLUMN_WIDTH (f) * WINDOW_LEFT_EDGE_COL (win);
857           p.y = FRAME_LINE_HEIGHT (f) * WINDOW_TOP_EDGE_LINE (win);
858         }
860       p.x += XINT (x); p.y += XINT (y);
862       XSETFRAME (Vmenu_updating_frame, f);
863     }
864   else
865     {      /* no position given */
866       /* FIXME: if called during dump, we need to stop precomputation of
867          key equivalents (see below) because the keydefs in ns-win.el have
868          not been loaded yet. */
869       if (noninteractive)
870         return Qnil;
871       Vmenu_updating_frame = Qnil;
872     }
874   /* now parse the lisp menus */
875   record_unwind_protect (unuse_menu_items, Qnil);
876   title = Qnil;
877   GCPRO1 (title);
879   /* Decode the menu items from what was specified.  */
881   keymap = get_keymap (menu, 0, 0);
882   if (CONSP (keymap))
883     {
884       /* We were given a keymap.  Extract menu info from the keymap.  */
885       Lisp_Object prompt;
887       /* Extract the detailed info to make one pane.  */
888       keymap_panes (&menu, 1, NILP (position));
890       /* Search for a string appearing directly as an element of the keymap.
891          That string is the title of the menu.  */
892       prompt = Fkeymap_prompt (keymap);
893       title = NILP (prompt) ? build_string ("Select") : prompt;
895       /* Make that be the pane title of the first pane.  */
896       if (!NILP (prompt) && menu_items_n_panes >= 0)
897         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt;
899       keymaps = 1;
900     }
901   else if (CONSP (menu) && KEYMAPP (XCAR (menu)))
902     {
903       /* We were given a list of keymaps.  */
904       int nmaps = XFASTINT (Flength (menu));
905       Lisp_Object *maps
906         = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object));
907       int i;
909       title = Qnil;
911       /* The first keymap that has a prompt string
912          supplies the menu title.  */
913       for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem))
914         {
915           Lisp_Object prompt;
917           maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0);
919           prompt = Fkeymap_prompt (keymap);
920           if (NILP (title) && !NILP (prompt))
921             title = prompt;
922         }
924       /* Extract the detailed info to make one pane.  */
925       keymap_panes (maps, nmaps, NILP (position));
927       /* Make the title be the pane title of the first pane.  */
928       if (!NILP (title) && menu_items_n_panes >= 0)
929         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title;
931       keymaps = 1;
932     }
933   else
934     {
935       /* We were given an old-fashioned menu.  */
936       title = Fcar (menu);
937       CHECK_STRING (title);
939       list_of_panes (Fcdr (menu));
941       keymaps = 0;
942     }
944   unbind_to (specpdl_count, Qnil);
946   /* If no position given, that was a signal to just precompute and cache
947      key equivalents, which was a side-effect of what we just did. */
948   if (NILP (position))
949     {
950       discard_menu_items ();
951       UNGCPRO;
952       return Qnil;
953     }
955   record_unwind_protect (cleanup_popup_menu, Qnil);
956   BLOCK_INPUT;
958   /* now parse stage 2 as in ns_update_menubar */
959   wv = xmalloc_widget_value ();
960   wv->name = "contextmenu";
961   wv->value = 0;
962   wv->enabled = 1;
963   wv->button_type = BUTTON_TYPE_NONE;
964   wv->help = Qnil;
965   first_wv = wv;
967   specpdl_count2 = SPECPDL_INDEX ();
969 #if 0
970   /* FIXME: a couple of one-line differences prevent reuse */
971   wv = digest_single_submenu (0, menu_items_used, Qnil);
972 #else
973   {
974   widget_value *save_wv = 0, *prev_wv = 0;
975   widget_value **submenu_stack
976     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
977 /*   Lisp_Object *subprefix_stack
978        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
979   int submenu_depth = 0;
980   int first_pane = 1;
981   int i;
983   /* Loop over all panes and items, filling in the tree.  */
984   i = 0;
985   while (i < menu_items_used)
986     {
987       if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
988         {
989           submenu_stack[submenu_depth++] = save_wv;
990           save_wv = prev_wv;
991           prev_wv = 0;
992           first_pane = 1;
993           i++;
994         }
995       else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
996         {
997           prev_wv = save_wv;
998           save_wv = submenu_stack[--submenu_depth];
999           first_pane = 0;
1000           i++;
1001         }
1002       else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
1003                && submenu_depth != 0)
1004         i += MENU_ITEMS_PANE_LENGTH;
1005       /* Ignore a nil in the item list.
1006          It's meaningful only for dialog boxes.  */
1007       else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
1008         i += 1;
1009       else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
1010         {
1011           /* Create a new pane.  */
1012           Lisp_Object pane_name, prefix;
1013           char *pane_string;
1015           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
1016           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
1018 #ifndef HAVE_MULTILINGUAL_MENU
1019           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
1020             {
1021               pane_name = ENCODE_MENU_STRING (pane_name);
1022               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
1023             }
1024 #endif
1025           pane_string = (NILP (pane_name)
1026                          ? "" : (char *) SDATA (pane_name));
1027           /* If there is just one top-level pane, put all its items directly
1028              under the top-level menu.  */
1029           if (menu_items_n_panes == 1)
1030             pane_string = "";
1032           /* If the pane has a meaningful name,
1033              make the pane a top-level menu item
1034              with its items as a submenu beneath it.  */
1035           if (!keymaps && strcmp (pane_string, ""))
1036             {
1037               wv = xmalloc_widget_value ();
1038               if (save_wv)
1039                 save_wv->next = wv;
1040               else
1041                 first_wv->contents = wv;
1042               wv->name = pane_string;
1043               if (keymaps && !NILP (prefix))
1044                 wv->name++;
1045               wv->value = 0;
1046               wv->enabled = 1;
1047               wv->button_type = BUTTON_TYPE_NONE;
1048               wv->help = Qnil;
1049               save_wv = wv;
1050               prev_wv = 0;
1051             }
1052           else if (first_pane)
1053             {
1054               save_wv = wv;
1055               prev_wv = 0;
1056             }
1057           first_pane = 0;
1058           i += MENU_ITEMS_PANE_LENGTH;
1059         }
1060       else
1061         {
1062           /* Create a new item within current pane.  */
1063           Lisp_Object item_name, enable, descrip, def, type, selected, help;
1064           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
1065           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
1066           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
1067           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
1068           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
1069           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
1070           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
1072 #ifndef HAVE_MULTILINGUAL_MENU
1073           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
1074             {
1075               item_name = ENCODE_MENU_STRING (item_name);
1076               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
1077             }
1079           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
1080             {
1081               descrip = ENCODE_MENU_STRING (descrip);
1082               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
1083             }
1084 #endif /* not HAVE_MULTILINGUAL_MENU */
1086           wv = xmalloc_widget_value ();
1087           if (prev_wv)
1088             prev_wv->next = wv;
1089           else
1090             save_wv->contents = wv;
1091           wv->name = (char *) SDATA (item_name);
1092           if (!NILP (descrip))
1093             wv->key = (char *) SDATA (descrip);
1094           wv->value = 0;
1095           /* If this item has a null value,
1096              make the call_data null so that it won't display a box
1097              when the mouse is on it.  */
1098           wv->call_data
1099               = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
1100           wv->enabled = !NILP (enable);
1102           if (NILP (type))
1103             wv->button_type = BUTTON_TYPE_NONE;
1104           else if (EQ (type, QCtoggle))
1105             wv->button_type = BUTTON_TYPE_TOGGLE;
1106           else if (EQ (type, QCradio))
1107             wv->button_type = BUTTON_TYPE_RADIO;
1108           else
1109             abort ();
1111           wv->selected = !NILP (selected);
1113           if (! STRINGP (help))
1114             help = Qnil;
1116           wv->help = help;
1118           prev_wv = wv;
1120           i += MENU_ITEMS_ITEM_LENGTH;
1121         }
1122     }
1123   }
1124 #endif
1126   if (!NILP (title))
1127     {
1128       widget_value *wv_title = xmalloc_widget_value ();
1129       widget_value *wv_sep = xmalloc_widget_value ();
1131       /* Maybe replace this separator with a bitmap or owner-draw item
1132          so that it looks better.  Having two separators looks odd.  */
1133       wv_sep->name = "--";
1134       wv_sep->next = first_wv->contents;
1135       wv_sep->help = Qnil;
1137 #ifndef HAVE_MULTILINGUAL_MENU
1138       if (STRING_MULTIBYTE (title))
1139         title = ENCODE_MENU_STRING (title);
1140 #endif
1142       wv_title->name = (char *) SDATA (title);
1143       wv_title->enabled = NO;
1144       wv_title->button_type = BUTTON_TYPE_NONE;
1145       wv_title->help = Qnil;
1146       wv_title->next = wv_sep;
1147       first_wv->contents = wv_title;
1148     }
1150   pmenu = [[EmacsMenu alloc] initWithTitle:
1151                                [NSString stringWithUTF8String: SDATA (title)]];
1152   [pmenu fillWithWidgetValue: first_wv->contents];
1153   free_menubar_widget_value_tree (first_wv);
1154   unbind_to (specpdl_count2, Qnil);
1156   popup_activated_flag = 1;
1157   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1158   popup_activated_flag = 0;
1159   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1161   UNBLOCK_INPUT;
1162   discard_menu_items ();
1163   unbind_to (specpdl_count, Qnil);
1164   UNGCPRO;
1166   if (error_name) error (error_name);
1167   return tem;
1173 /* ==========================================================================
1175     Toolbar: externally-called functions
1177    ========================================================================== */
1179 void
1180 free_frame_tool_bar (FRAME_PTR f)
1181 /* --------------------------------------------------------------------------
1182     Under NS we just hide the toolbar until it might be needed again.
1183    -------------------------------------------------------------------------- */
1185   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1188 void
1189 update_frame_tool_bar (FRAME_PTR f)
1190 /* --------------------------------------------------------------------------
1191     Update toolbar contents
1192    -------------------------------------------------------------------------- */
1194   int i;
1195   EmacsToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar];
1197   [toolbar clearActive];
1199   /* update EmacsToolbar as in GtkUtils, build items list */
1200   for (i = 0; i < f->n_tool_bar_items; ++i)
1201     {
1202 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1203                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1205       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1206       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1207       int idx;
1208       int img_id;
1209       struct image *img;
1210       Lisp_Object image;
1211       Lisp_Object helpObj;
1212       char *helpText;
1214       /* If image is a vector, choose the image according to the
1215          button state.  */
1216       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1217       if (VECTORP (image))
1218         {
1219           /* NS toolbar auto-computes disabled and selected images */
1220           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1221           xassert (ASIZE (image) >= idx);
1222           image = AREF (image, idx);
1223         }
1224       else
1225         {
1226           idx = -1;
1227         }
1228       /* Ignore invalid image specifications.  */
1229       if (!valid_image_p (image))
1230         {
1231           NSLog (@"Invalid image for toolbar item");
1232           continue;
1233         }
1235       img_id = lookup_image (f, image);
1236       img = IMAGE_FROM_ID (f, img_id);
1237       prepare_image_for_display (f, img);
1239       if (img->load_failed_p || img->pixmap == nil)
1240         {
1241           NSLog (@"Could not prepare toolbar image for display.");
1242           continue;
1243         }
1245       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1246       if (NILP (helpObj))
1247         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1248       helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1250       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1251                                enabled: enabled_p];
1252 #undef TOOLPROP
1253     }
1255   if (![toolbar isVisible])
1256       [toolbar setVisible: YES];
1258   if ([toolbar changed])
1259     {
1260       /* inform app that toolbar has changed */
1261       NSDictionary *dict = [toolbar configurationDictionary];
1262       NSMutableDictionary *newDict = [dict mutableCopy];
1263       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1264       NSObject *key;
1265       while ((key = [keys nextObject]) != nil)
1266         {
1267           NSObject *val = [dict objectForKey: key];
1268           if ([val isKindOfClass: [NSArray class]])
1269             {
1270               [newDict setObject:
1271                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1272                           forKey: key];
1273               break;
1274             }
1275         }
1276       [toolbar setConfigurationFromDictionary: newDict];
1277       [newDict release];
1278     }
1283 /* ==========================================================================
1285     Toolbar: class implementation
1287    ========================================================================== */
1289 @implementation EmacsToolbar
1291 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1293   self = [super initWithIdentifier: identifier];
1294   emacsView = view;
1295   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1296   [self setSizeMode: NSToolbarSizeModeSmall];
1297   [self setDelegate: self];
1298   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1299   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1300   prevEnablement = enablement = 0L;
1301   return self;
1304 - (void)dealloc
1306   [prevIdentifiers release];
1307   [activeIdentifiers release];
1308   [identifierToItem release];
1309   [super dealloc];
1312 - (void) clearActive
1314   [prevIdentifiers release];
1315   prevIdentifiers = [activeIdentifiers copy];
1316   [activeIdentifiers removeAllObjects];
1317   prevEnablement = enablement;
1318   enablement = 0L;
1321 - (BOOL) changed
1323   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1324     enablement == prevEnablement ? NO : YES;
1327 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1328                         helpText: (char *)help enabled: (BOOL)enabled
1330   /* 1) come up w/identifier */
1331   NSString *identifier
1332       = [NSString stringWithFormat: @"%u", [img hash]];
1334   /* 2) create / reuse item */
1335   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1336   if (item == nil)
1337     {
1338       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1339                autorelease];
1340       [item setImage: img];
1341       [item setToolTip: [NSString stringWithCString: help]];
1342       [item setTarget: emacsView];
1343       [item setAction: @selector (toolbarClicked:)];
1344     }
1346   [item setTag: idx];
1347   [item setEnabled: enabled];
1349   /* 3) update state */
1350   [identifierToItem setObject: item forKey: identifier];
1351   [activeIdentifiers addObject: identifier];
1352   enablement = (enablement << 1) | (enabled == YES);
1355 /* This overrides super's implementation, which automatically sets
1356    all items to enabled state (for some reason). */
1357 - (void)validateVisibleItems { }
1360 /* delegate methods */
1362 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1363       itemForItemIdentifier: (NSString *)itemIdentifier
1364   willBeInsertedIntoToolbar: (BOOL)flag
1366   /* look up NSToolbarItem by identifier and return... */
1367   return [identifierToItem objectForKey: itemIdentifier];
1370 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1372   /* return entire set.. */
1373   return activeIdentifiers;
1376 /* for configuration palette (not yet supported) */
1377 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1379   /* return entire set... */
1380   return [identifierToItem allKeys];
1383 /* optional and unneeded */
1384 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1385 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1386 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1388 @end  /* EmacsToolbar */
1392 /* ==========================================================================
1394     Tooltip: class implementation
1396    ========================================================================== */
1398 /* Needed because NeXTstep does not provide enough control over tooltip
1399    display. */
1400 @implementation EmacsTooltip
1402 - init
1404   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1405                                             blue: 0.792 alpha: 0.95];
1406   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1407   NSFont *sfont = [font screenFont];
1408   int height = [sfont ascender] - [sfont descender];
1409 /*[font boundingRectForFont].size.height; */
1410   NSRect r = NSMakeRect (0, 0, 100, height+6);
1412   textField = [[NSTextField alloc] initWithFrame: r];
1413   [textField setFont: font];
1414   [textField setBackgroundColor: col];
1416   [textField setEditable: NO];
1417   [textField setSelectable: NO];
1418   [textField setBordered: YES];
1419   [textField setBezeled: YES];
1420   [textField setDrawsBackground: YES];
1422   win = [[NSWindow alloc]
1423             initWithContentRect: [textField frame]
1424                       styleMask: 0
1425                         backing: NSBackingStoreBuffered
1426                           defer: YES];
1427   [win setReleasedWhenClosed: NO];
1428   [win setDelegate: self];
1429   [[win contentView] addSubview: textField];
1430 /*  [win setBackgroundColor: col]; */
1431   [win setOpaque: NO];
1433   return self;
1436 - (void) dealloc
1438   [win close];
1439   [win release];
1440   [textField release];
1441   [super dealloc];
1444 - (void) setText: (char *)text
1446   NSString *str = [NSString stringWithUTF8String: text];
1447   NSRect r = [textField frame];
1448   NSSize textSize = [str sizeWithAttributes: 
1449      [NSDictionary dictionaryWithObject: [[textField font] screenFont]
1450                                  forKey: NSFontAttributeName]];
1451   NSSize padSize = [[[textField font] screenFont] 
1452                      boundingRectForFont].size;
1454   r.size.width = textSize.width + padSize.width/2;
1455   r.size.height = textSize.height + padSize.height/2;
1456   [textField setFrame: r];
1457   [textField setStringValue: str];
1460 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1462   NSRect wr = [win frame];
1464   wr.origin = NSMakePoint (x, y);
1465   wr.size = [textField frame].size;
1467   [win setFrame: wr display: YES];
1468   [win orderFront: self];
1469   [win display];
1470   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1471                                          selector: @selector (hide)
1472                                          userInfo: nil repeats: NO];
1473   [timer retain];
1476 - (void) hide
1478   [win close];
1479   if (timer != nil)
1480     {
1481       if ([timer isValid])
1482         [timer invalidate];
1483       [timer release];
1484       timer = nil;
1485     }
1488 - (BOOL) isActive
1490   return timer != nil;
1493 - (NSRect) frame
1495   return [textField frame];
1498 @end  /* EmacsTooltip */
1502 /* ==========================================================================
1504     Popup Dialog: implementing functions
1506    ========================================================================== */
1509 static Lisp_Object
1510 pop_down_menu (Lisp_Object arg)
1512   struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1513   if (popup_activated_flag)
1514     {
1515       popup_activated_flag = 0;
1516       BLOCK_INPUT;
1517       [NSApp endModalSession: popupSession];
1518       [((EmacsDialogPanel *) (p->pointer)) close];
1519       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1520       UNBLOCK_INPUT;
1521     }
1522   return Qnil;
1526 Lisp_Object
1527 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1529   id dialog;
1530   Lisp_Object window, tem;
1531   struct frame *f;
1532   NSPoint p;
1533   BOOL isQ;
1535   NSTRACE (x-popup-dialog);
1536   
1537   check_ns ();
1539   isQ = NILP (header);
1541   if (EQ (position, Qt)
1542       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1543                                || EQ (XCAR (position), Qtool_bar))))
1544     {
1545       window = selected_window;
1546     }
1547   else if (CONSP (position))
1548     {
1549       Lisp_Object tem;
1550       tem = Fcar (position);
1551       if (XTYPE (tem) == Lisp_Cons)
1552         window = Fcar (Fcdr (position));
1553       else
1554         {
1555           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1556           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1557         }
1558     }
1559   else if (WINDOWP (position) || FRAMEP (position))
1560     {
1561       window = position;
1562     }
1563   else
1564     window = Qnil;
1566   if (FRAMEP (window))
1567     f = XFRAME (window);
1568   else if (WINDOWP (window))
1569     {
1570       CHECK_LIVE_WINDOW (window);
1571       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1572     }
1573   else
1574     CHECK_WINDOW (window);
1576   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1577   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1579   BLOCK_INPUT;
1580   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1581                                            isQuestion: isQ];
1582   {
1583     int specpdl_count = SPECPDL_INDEX ();
1584     record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1585     popup_activated_flag = 1;
1586     tem = [dialog runDialogAt: p];
1587     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1588   }
1589   UNBLOCK_INPUT;
1591   return tem;
1595 /* ==========================================================================
1597     Popup Dialog: class implementation
1599    ========================================================================== */
1601 @interface FlippedView : NSView
1604 @end
1606 @implementation FlippedView
1607 - (BOOL)isFlipped
1609   return YES;
1611 @end
1613 @implementation EmacsDialogPanel
1615 #define SPACER          8.0
1616 #define ICONSIZE        64.0
1617 #define TEXTHEIGHT      20.0
1618 #define MINCELLWIDTH    90.0
1620 - initWithContentRect: (NSRect)contentRect styleMask: (unsigned int)aStyle
1621               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1623   NSSize spacing = {SPACER, SPACER};
1624   NSRect area;
1625   char this_cmd_name[80];
1626   id cell;
1627   static NSImageView *imgView;
1628   static FlippedView *contentView;
1630   if (imgView == nil)
1631     {
1632       NSImage *img;
1633       area.origin.x   = 3*SPACER;
1634       area.origin.y   = 2*SPACER;
1635       area.size.width = ICONSIZE;
1636       area.size.height= ICONSIZE;
1637       img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1638       [img setScalesWhenResized: YES];
1639       [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1640       imgView = [[NSImageView alloc] initWithFrame: area];
1641       [imgView setImage: img];
1642       [imgView setEditable: NO];
1643       [img release];
1644     }
1646   aStyle = NSTitledWindowMask;
1647   flag = YES;
1648   rows = 0;
1649   cols = 1;
1650   [super initWithContentRect: contentRect styleMask: aStyle
1651                      backing: backingType defer: flag];
1652   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1653   [self setContentView: contentView];
1655   [[self contentView] setAutoresizesSubviews: YES];
1657   [[self contentView] addSubview: imgView];
1658   [self setTitle: @""];
1660   area.origin.x   += ICONSIZE+2*SPACER;
1661 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1662   area.size.width = 400;
1663   area.size.height= TEXTHEIGHT;
1664   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1665   [[self contentView] addSubview: command];
1666   [command setStringValue: @"Emacs"];
1667   [command setDrawsBackground: NO];
1668   [command setBezeled: NO];
1669   [command setSelectable: NO];
1670   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1672 /*  area.origin.x   = ICONSIZE+2*SPACER;
1673   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1674   area.size.width = 400;
1675   area.size.height= 2;
1676   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1677   [[self contentView] addSubview: tem];
1678   [tem setTitlePosition: NSNoTitle];
1679   [tem setAutoresizingMask: NSViewWidthSizable];*/
1681 /*  area.origin.x = ICONSIZE+2*SPACER; */
1682   area.origin.y += TEXTHEIGHT+SPACER;
1683   area.size.width = 400;
1684   area.size.height= TEXTHEIGHT;
1685   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1686   [[self contentView] addSubview: title];
1687   [title setDrawsBackground: NO];
1688   [title setBezeled: NO];
1689   [title setSelectable: NO];
1690   [title setFont: [NSFont systemFontOfSize: 11.0]];
1692   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1693   [cell setBordered: NO];
1694   [cell setEnabled: NO];
1695   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1696   [cell setBezelStyle: NSRoundedBezelStyle];
1698   matrix = [[NSMatrix alloc] initWithFrame: contentRect 
1699                                       mode: NSHighlightModeMatrix 
1700                                  prototype: cell 
1701                               numberOfRows: 0 
1702                            numberOfColumns: 1];
1703   [[self contentView] addSubview: matrix];
1704   [matrix release];
1705   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1706                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1707   [matrix setIntercellSpacing: spacing];
1709   [self setOneShot: YES];
1710   [self setReleasedWhenClosed: YES];
1711   [self setHidesOnDeactivate: YES];
1712   return self;
1716 - (BOOL)windowShouldClose: (id)sender
1718   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1719   return NO;
1723 void process_dialog (id window, Lisp_Object list)
1725   Lisp_Object item;
1726   int row = 0;
1728   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1729     {
1730       item = XCAR (list);
1731       if (XTYPE (item) == Lisp_String)
1732         {
1733           [window addString: SDATA (item) row: row++];
1734         }
1735       else if (XTYPE (item) == Lisp_Cons)
1736         {
1737           [window addButton: SDATA (XCAR (item))
1738                       value: XCDR (item) row: row++];
1739         }
1740       else if (NILP (item))
1741         {
1742           [window addSplit];
1743           row = 0;
1744         }
1745     }
1749 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1751   id cell;
1752        
1753   if (row >= rows)
1754     {
1755       [matrix addRow];
1756       rows++;
1757     }
1758   cell = [matrix cellAtRow: row column: cols-1];
1759   [cell setTarget: self];
1760   [cell setAction: @selector (clicked: )];
1761   [cell setTitle: [NSString stringWithUTF8String: str]];
1762   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1763   [cell setBordered: YES];
1764   [cell setEnabled: YES];
1766   return self;
1770 - addString: (char *)str row: (int)row
1772   id cell;
1773        
1774   if (row >= rows)
1775     {
1776       [matrix addRow];
1777       rows++;
1778     }
1779   cell = [matrix cellAtRow: row column: cols-1];
1780   [cell setTitle: [NSString stringWithUTF8String: str]];
1781   [cell setBordered: YES];
1782   [cell setEnabled: NO];
1784   return self;
1788 - addSplit
1790   [matrix addColumn];
1791   cols++;
1792   return self;
1796 - clicked: sender
1798   NSArray *sellist = nil;
1799   EMACS_INT seltag;
1801   sellist = [sender selectedCells];
1802   if ([sellist count]<1) 
1803     return self;
1805   seltag = [[sellist objectAtIndex: 0] tag];
1806   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1807     [NSApp stopModalWithCode: seltag];
1808   return self;
1812 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1814   Lisp_Object head;
1815   [super init];
1817   if (XTYPE (contents) == Lisp_Cons)
1818     {
1819       head = Fcar (contents);
1820       process_dialog (self, Fcdr (contents));
1821     }
1822   else
1823     head = contents;
1825   if (XTYPE (head) == Lisp_String)
1826       [title setStringValue:
1827                  [NSString stringWithUTF8String: SDATA (head)]];
1828   else if (isQ == YES)
1829       [title setStringValue: @"Question"];
1830   else
1831       [title setStringValue: @"Information"];
1833   {
1834     int i;
1835     NSRect r, s, t;
1837     if (cols == 1 && rows > 1)  /* Never told where to split */
1838       {
1839         [matrix addColumn];
1840         for (i = 0; i<rows/2; i++)
1841           {
1842             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1843                       atRow: i column: 1];
1844             [matrix removeRow: (rows+1)/2];
1845           }
1846       }
1848     [matrix sizeToFit];
1849     {
1850       NSSize csize = [matrix cellSize];
1851       if (csize.width < MINCELLWIDTH)
1852         {
1853           csize.width = MINCELLWIDTH;
1854           [matrix setCellSize: csize];
1855           [matrix sizeToCells];
1856         }
1857     }
1859     [title sizeToFit];
1860     [command sizeToFit];
1862     t = [matrix frame];
1863     r = [title frame];
1864     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1865       {
1866         t.origin.x   = r.origin.x;
1867         t.size.width = r.size.width;
1868       }
1869     r = [command frame];
1870     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1871       {
1872         t.origin.x   = r.origin.x;
1873         t.size.width = r.size.width;
1874       }
1876     r = [self frame];
1877     s = [(NSView *)[self contentView] frame];
1878     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1879     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1880     [self setFrame: r display: NO];
1881   }
1883   return self;
1887 - (void)dealloc
1889   { [super dealloc]; return; };
1893 - (Lisp_Object)runDialogAt: (NSPoint)p
1895   int ret;
1896   extern EMACS_TIME timer_check (int do_it_now); /* TODO: add to a header */
1898   /* initiate a session that will be ended by pop_down_menu */
1899   popupSession = [NSApp beginModalSessionForWindow: self];
1900   while (popup_activated_flag
1901          && (ret = [NSApp runModalSession: popupSession])
1902               == NSRunContinuesResponse)
1903     {
1904       /* Run this for timers.el, indep of atimers; might not return.
1905          TODO: use return value to avoid calling every iteration. */
1906       timer_check (1);
1907       [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1908     }
1910   {                             /* FIXME: BIG UGLY HACK!!! */
1911       Lisp_Object tmp;
1912       *(EMACS_INT*)(&tmp) = ret;
1913       return tmp;
1914   }
1917 @end
1920 /* ==========================================================================
1922     Lisp definitions
1924    ========================================================================== */
1926 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1927        doc: /* Cause the NS menu to be re-calculated.  */)
1928      ()
1930   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1931   return Qnil;
1935 DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
1936        doc: /* Pop up a deck-of-cards menu and return user's selection.
1937 POSITION is a position specification.  This is either a mouse button event
1938 or a list ((XOFFSET YOFFSET) WINDOW)
1939 where XOFFSET and YOFFSET are positions in pixels from the top left
1940 corner of WINDOW.  (WINDOW may be a window or a frame object.)
1941 This controls the position of the top left of the menu as a whole.
1942 If POSITION is t, it means to use the current mouse position.
1944 MENU is a specifier for a menu.  For the simplest case, MENU is a keymap.
1945 The menu items come from key bindings that have a menu string as well as
1946 a definition; actually, the \"definition\" in such a key binding looks like
1947 \(STRING . REAL-DEFINITION).  To give the menu a title, put a string into
1948 the keymap as a top-level element.
1950 If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.
1951 Otherwise, REAL-DEFINITION should be a valid key binding definition.
1953 You can also use a list of keymaps as MENU.
1954   Then each keymap makes a separate pane.
1956 When MENU is a keymap or a list of keymaps, the return value is the
1957 list of events corresponding to the user's choice. Note that
1958 `x-popup-menu' does not actually execute the command bound to that
1959 sequence of events.
1961 Alternatively, you can specify a menu of multiple panes
1962   with a list of the form (TITLE PANE1 PANE2...),
1963 where each pane is a list of form (TITLE ITEM1 ITEM2...).
1964 Each ITEM is normally a cons cell (STRING . VALUE);
1965 but a string can appear as an item--that makes a nonselectable line
1966 in the menu.
1967 With this form of menu, the return value is VALUE from the chosen item.  */)
1968      (position, menu)
1969      Lisp_Object position, menu;
1971   return ns_popup_menu (position, menu);
1975 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1976        doc: /* Pop up a dialog box and return user's selection.
1977 POSITION specifies which frame to use.
1978 This is normally a mouse button event or a window or frame.
1979 If POSITION is t, it means to use the frame the mouse is on.
1980 The dialog box appears in the middle of the specified frame.
1982 CONTENTS specifies the alternatives to display in the dialog box.
1983 It is a list of the form (DIALOG ITEM1 ITEM2...).
1984 Each ITEM is a cons cell (STRING . VALUE).
1985 The return value is VALUE from the chosen item.
1987 An ITEM may also be just a string--that makes a nonselectable item.
1988 An ITEM may also be nil--that means to put all preceding items
1989 on the left of the dialog box and all following items on the right.
1990 \(By default, approximately half appear on each side.)
1992 If HEADER is non-nil, the frame title for the box is "Information",
1993 otherwise it is "Question".
1995 If the user gets rid of the dialog box without making a valid choice,
1996 for instance using the window manager, then this produces a quit and
1997 `x-popup-dialog' does not return.  */)
1998      (position, contents, header)
1999      Lisp_Object position, contents, header;
2001   return ns_popup_dialog (position, contents, header);
2004 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
2005        doc: /* Return t if a menu or popup dialog is active.  */)
2006      ()
2008   return popup_activated () ? Qt : Qnil;
2011 /* ==========================================================================
2013     Lisp interface declaration
2015    ========================================================================== */
2017 void
2018 syms_of_nsmenu ()
2020   defsubr (&Sx_popup_menu);
2021   defsubr (&Sx_popup_dialog);
2022   defsubr (&Sns_reset_menu);
2023   defsubr (&Smenu_or_popup_active_p);
2024   staticpro (&menu_items);
2025   menu_items = Qnil;
2027   Qdebug_on_next_call = intern ("debug-on-next-call");
2028   staticpro (&Qdebug_on_next_call);
2031 // arch-tag: 75773656-52e5-4c44-a398-47bd87b32619