Add Bug#.
[emacs.git] / src / nsmenu.m
blob07cc3f0fcc3196e4c1863cb5f369bbe68c9455a1
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"
27 #include <setjmp.h>
29 #include "lisp.h"
30 #include "window.h"
31 #include "buffer.h"
32 #include "keymap.h"
33 #include "coding.h"
34 #include "commands.h"
35 #include "blockinput.h"
36 #include "nsterm.h"
37 #include "termhooks.h"
38 #include "keyboard.h"
40 #define NSMENUPROFILE 0
42 #if NSMENUPROFILE
43 #include <sys/timeb.h>
44 #include <sys/types.h>
45 #endif
47 #define MenuStagger 10.0
49 #if 0
50 int menu_trace_num = 0;
51 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
52                                 __FILE__, __LINE__, ++menu_trace_num)
53 #else
54 #define NSTRACE(x)
55 #endif
57 #if 0
58 /* Include lisp -> C common menu parsing code */
59 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
60 #include "nsmenu_common.c"
61 #endif
63 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
64 extern Lisp_Object QCtoggle, QCradio;
66 extern Lisp_Object Vmenu_updating_frame;
68 Lisp_Object Qdebug_on_next_call;
69 extern Lisp_Object Voverriding_local_map, Voverriding_local_map_menu_flag,
70                    Qoverriding_local_map, Qoverriding_terminal_local_map;
72 extern long context_menu_value;
73 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
75 /* Nonzero means a menu is currently active.  */
76 static int popup_activated_flag;
77 static NSModalSession popupSession;
79 /* NOTE: toolbar implementation is at end,
80   following complete menu implementation. */
83 /* ==========================================================================
85     Menu: Externally-called functions
87    ========================================================================== */
90 /* FIXME: not currently used, but should normalize with other terms. */
91 void
92 x_activate_menubar (struct frame *f)
94     fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
98 /* Supposed to discard menubar and free storage.  Since we share the
99    menubar among frames and update its context for the focused window,
100    there is nothing to do here. */
101 void
102 free_frame_menubar (struct frame *f)
104   return;
109 popup_activated ()
111   return popup_activated_flag;
115 /* --------------------------------------------------------------------------
116     Update menubar.  Three cases:
117     1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
118        just top-level menu strings (OS X), or goto case (2) (GNUstep).
119     2) deep_p = 1, submenu = nil: Recompute all submenus.
120     3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
121    -------------------------------------------------------------------------- */
122 void
123 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
125   NSAutoreleasePool *pool;
126   id menu = [NSApp mainMenu];
127   static EmacsMenu *last_submenu = nil;
128   BOOL needsSet = NO;
129   const char *submenuTitle = [[submenu title] UTF8String];
130   extern int waiting_for_input;
131   int owfi;
132   Lisp_Object items;
133   widget_value *wv, *first_wv, *prev_wv = 0;
134   int i;
136 #if NSMENUPROFILE
137   struct timeb tb;
138   long t;
139 #endif
141   NSTRACE (set_frame_menubar);
143   if (f != SELECTED_FRAME ())
144       return;
145   XSETFRAME (Vmenu_updating_frame, f);
146 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
148   BLOCK_INPUT;
149   pool = [[NSAutoreleasePool alloc] init];
151   /* Menu may have been created automatically; if so, discard it. */
152   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
153     {
154       [menu release];
155       menu = nil;
156     }
158   if (menu == nil)
159     {
160       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
161       needsSet = YES;
162     }
163   else
164     {  /* close up anything on there */
165       id attMenu = [menu attachedMenu];
166       if (attMenu != nil)
167         [attMenu close];
168     }
170 #if NSMENUPROFILE
171   ftime (&tb);
172   t = -(1000*tb.time+tb.millitm);
173 #endif
175 #ifdef NS_IMPL_GNUSTEP
176   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
177 #endif
179   if (deep_p)
180     {
181       /* Fully parse one or more of the submenus. */
182       int n = 0;
183       int *submenu_start, *submenu_end;
184       int *submenu_top_level_items, *submenu_n_panes;
185       struct buffer *prev = current_buffer;
186       Lisp_Object buffer;
187       int specpdl_count = SPECPDL_INDEX ();
188       int previous_menu_items_used = f->menu_bar_items_used;
189       Lisp_Object *previous_items
190         = (Lisp_Object *) alloca (previous_menu_items_used
191                                   * sizeof (Lisp_Object));
193       /* lisp preliminaries */
194       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
195       specbind (Qinhibit_quit, Qt);
196       specbind (Qdebug_on_next_call, Qnil);
197       record_unwind_save_match_data ();
198       if (NILP (Voverriding_local_map_menu_flag))
199         {
200           specbind (Qoverriding_terminal_local_map, Qnil);
201           specbind (Qoverriding_local_map, Qnil);
202         }
203       set_buffer_internal_1 (XBUFFER (buffer));
205       /* TODO: for some reason this is not needed in other terms,
206            but some menu updates call Info-extract-pointer which causes
207            abort-on-error if waiting-for-input.  Needs further investigation. */
208       owfi = waiting_for_input;
209       waiting_for_input = 0;
211       /* lucid hook and possible reset */
212       safe_run_hooks (Qactivate_menubar_hook);
213       if (! NILP (Vlucid_menu_bar_dirty_flag))
214         call0 (Qrecompute_lucid_menubar);
215       safe_run_hooks (Qmenu_bar_update_hook);
216       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
218       /* Now ready to go */
219       items = FRAME_MENU_BAR_ITEMS (f);
221       /* Save the frame's previous menu bar contents data */
222       if (previous_menu_items_used)
223         bcopy (XVECTOR (f->menu_bar_vector)->contents, previous_items,
224                previous_menu_items_used * sizeof (Lisp_Object));
226       /* parse stage 1: extract from lisp */
227       save_menu_items ();
229       menu_items = f->menu_bar_vector;
230       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
231       submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
232       submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
233       submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
234       submenu_top_level_items
235         = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
236       init_menu_items ();
237       for (i = 0; i < XVECTOR (items)->size; i += 4)
238         {
239           Lisp_Object key, string, maps;
241           key = XVECTOR (items)->contents[i];
242           string = XVECTOR (items)->contents[i + 1];
243           maps = XVECTOR (items)->contents[i + 2];
244           if (NILP (string))
245             break;
247           /* FIXME: we'd like to only parse the needed submenu, but this
248                was causing crashes in the _common parsing code.. need to make
249                sure proper initialization done.. */
250 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
251              continue; */
253           submenu_start[i] = menu_items_used;
255           menu_items_n_panes = 0;
256           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
257           submenu_n_panes[i] = menu_items_n_panes;
258           submenu_end[i] = menu_items_used;
259           n++;
260         }
262       finish_menu_items ();
263       waiting_for_input = owfi;
266       if (submenu && n == 0)
267         {
268           /* should have found a menu for this one but didn't */
269           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
270                   submenuTitle);
271           discard_menu_items ();
272           unbind_to (specpdl_count, Qnil);
273           [pool release];
274           UNBLOCK_INPUT;
275           return;
276         }
278       /* parse stage 2: insert into lucid 'widget_value' structures
279          [comments in other terms say not to evaluate lisp code here] */
280       wv = xmalloc_widget_value ();
281       wv->name = "menubar";
282       wv->value = 0;
283       wv->enabled = 1;
284       wv->button_type = BUTTON_TYPE_NONE;
285       wv->help = Qnil;
286       first_wv = wv;
288       for (i = 0; i < 4*n; i += 4)
289         {
290           menu_items_n_panes = submenu_n_panes[i];
291           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
292                                       submenu_top_level_items[i]);
293           if (prev_wv)
294             prev_wv->next = wv;
295           else
296             first_wv->contents = wv;
297           /* Don't set wv->name here; GC during the loop might relocate it.  */
298           wv->enabled = 1;
299           wv->button_type = BUTTON_TYPE_NONE;
300           prev_wv = wv;
301         }
303       set_buffer_internal_1 (prev);
305       /* Compare the new menu items with previous, and leave off if no change */
306       /* FIXME: following other terms here, but seems like this should be
307            done before parse stage 2 above, since its results aren't used */
308       if (previous_menu_items_used
309           && (!submenu || (submenu && submenu == last_submenu))
310           && menu_items_used == previous_menu_items_used)
311         {
312           for (i = 0; i < previous_menu_items_used; i++)
313             /* FIXME: this ALWAYS fails on Buffers menu items.. something
314                  about their strings causes them to change every time, so we
315                  double-check failures */
316             if (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i]))
317               if (!(STRINGP (previous_items[i])
318                     && STRINGP (XVECTOR (menu_items)->contents[i])
319                     && !strcmp (SDATA (previous_items[i]),
320                                SDATA (XVECTOR (menu_items)->contents[i]))))
321                   break;
322           if (i == previous_menu_items_used)
323             {
324               /* No change.. */
326 #if NSMENUPROFILE
327               ftime (&tb);
328               t += 1000*tb.time+tb.millitm;
329               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
330 #endif
332               free_menubar_widget_value_tree (first_wv);
333               discard_menu_items ();
334               unbind_to (specpdl_count, Qnil);
335               [pool release];
336               UNBLOCK_INPUT;
337               return;
338             }
339         }
340       /* The menu items are different, so store them in the frame */
341       /* FIXME: this is not correct for single-submenu case */
342       f->menu_bar_vector = menu_items;
343       f->menu_bar_items_used = menu_items_used;
345       /* Calls restore_menu_items, etc., as they were outside */
346       unbind_to (specpdl_count, Qnil);
348       /* Parse stage 2a: now GC cannot happen during the lifetime of the
349          widget_value, so it's safe to store data from a Lisp_String */
350       wv = first_wv->contents;
351       for (i = 0; i < XVECTOR (items)->size; i += 4)
352         {
353           Lisp_Object string;
354           string = XVECTOR (items)->contents[i + 1];
355           if (NILP (string))
356             break;
357 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
358                continue; */
360           wv->name = (char *) SDATA (string);
361           update_submenu_strings (wv->contents);
362           wv = wv->next;
363         }
365       /* Now, update the NS menu; if we have a submenu, use that, otherwise
366          create a new menu for each sub and fill it. */
367       if (submenu)
368         {
369           for (wv = first_wv->contents; wv; wv = wv->next)
370             {
371               if (!strcmp (submenuTitle, wv->name))
372                 {
373                   [submenu fillWithWidgetValue: wv->contents];
374                   last_submenu = submenu;
375                   break;
376                 }
377             }
378         }
379       else
380         {
381           [menu fillWithWidgetValue: first_wv->contents];
382         }
384     }
385   else
386     {
387       static int n_previous_strings = 0;
388       static char previous_strings[100][10];
389       static struct frame *last_f = NULL;
390       int n;
391       Lisp_Object string;
393       wv = xmalloc_widget_value ();
394       wv->name = "menubar";
395       wv->value = 0;
396       wv->enabled = 1;
397       wv->button_type = BUTTON_TYPE_NONE;
398       wv->help = Qnil;
399       first_wv = wv;
401       /* Make widget-value tree w/ just the top level menu bar strings */
402       items = FRAME_MENU_BAR_ITEMS (f);
403       if (NILP (items))
404         {
405           [pool release];
406           UNBLOCK_INPUT;
407           return;
408         }
411       /* check if no change.. this mechanism is a bit rough, but ready */
412       n = XVECTOR (items)->size / 4;
413       if (f == last_f && n_previous_strings == n)
414         {
415           for (i = 0; i<n; i++)
416             {
417               string = AREF (items, 4*i+1);
419               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
420                 continue;
421               if (NILP (string))
422                 if (previous_strings[i][0])
423                   break;
424               else
425                 continue;
426               if (strncmp (previous_strings[i], SDATA (string), 10))
427                 break;
428             }
430           if (i == n)
431             {
432               [pool release];
433               UNBLOCK_INPUT;
434               return;
435             }
436         }
438       [menu clear];
439       for (i = 0; i < XVECTOR (items)->size; i += 4)
440         {
441           string = XVECTOR (items)->contents[i + 1];
442           if (NILP (string))
443             break;
445           if (n < 100)
446             strncpy (previous_strings[i/4], SDATA (string), 10);
448           wv = xmalloc_widget_value ();
449           wv->name = (char *) SDATA (string);
450           wv->value = 0;
451           wv->enabled = 1;
452           wv->button_type = BUTTON_TYPE_NONE;
453           wv->help = Qnil;
454           wv->call_data = (void *) (EMACS_INT) (-1);
456 #ifdef NS_IMPL_COCOA
457           /* we'll update the real copy under app menu when time comes */
458           if (!strcmp ("Services", wv->name))
459             {
460               /* but we need to make sure it will update on demand */
461               [svcsMenu setFrame: f];
462               [svcsMenu setDelegate: svcsMenu];
463             }
464           else
465 #endif
466           [menu addSubmenuWithTitle: wv->name forFrame: f];
468           if (prev_wv)
469             prev_wv->next = wv;
470           else
471             first_wv->contents = wv;
472           prev_wv = wv;
473         }
475       last_f = f;
476       if (n < 100)
477         n_previous_strings = n;
478       else
479         n_previous_strings = 0;
481     }
482   free_menubar_widget_value_tree (first_wv);
485 #if NSMENUPROFILE
486   ftime (&tb);
487   t += 1000*tb.time+tb.millitm;
488   fprintf (stderr, "Menu update took %ld msec.\n", t);
489 #endif
491   /* set main menu */
492   if (needsSet)
493     [NSApp setMainMenu: menu];
495   [pool release];
496   UNBLOCK_INPUT;
501 /* Main emacs core entry point for menubar menus: called to indicate that the
502    frame's menus have changed, and the *step representation should be updated
503    from Lisp. */
504 void
505 set_frame_menubar (struct frame *f, int first_time, int deep_p)
507   ns_update_menubar (f, deep_p, nil);
511 /* Utility (from macmenu.c): is this item a separator? */
512 static int
513 name_is_separator (name)
514      const char *name;
516   const char *start = name;
518   /* Check if name string consists of only dashes ('-').  */
519   while (*name == '-') name++;
520   /* Separators can also be of the form "--:TripleSuperMegaEtched"
521      or "--deep-shadow".  We don't implement them yet, se we just treat
522      them like normal separators.  */
523   return (*name == '\0' || start + 2 == name);
527 /* ==========================================================================
529     Menu: class implementation
531    ========================================================================== */
534 /* Menu that can define itself from Emacs "widget_value"s and will lazily
535    update itself when user clicked.  Based on Carbon/AppKit implementation
536    by Yamamoto Mitsuharu. */
537 @implementation EmacsMenu
539 /* override designated initializer */
540 - initWithTitle: (NSString *)title
542   if (self = [super initWithTitle: title])
543     [self setAutoenablesItems: NO];
544   return self;
548 /* used for top-level */
549 - initWithTitle: (NSString *)title frame: (struct frame *)f
551   [self initWithTitle: title];
552   frame = f;
553 #ifdef NS_IMPL_COCOA
554   [self setDelegate: self];
555 #endif
556   return self;
560 - (void)setFrame: (struct frame *)f
562   frame = f;
566 /* delegate method called when a submenu is being opened: run a 'deep' call
567    to set_frame_menubar */
568 - (void)menuNeedsUpdate: (NSMenu *)menu
570   NSEvent *event;
571   if (!FRAME_LIVE_P (frame))
572     return;
573   event = [[FRAME_NS_VIEW (frame) window] currentEvent];
574   /* HACK: Cocoa/Carbon will request update on every keystroke
575      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
576      since key equivalents are handled through emacs.
577      On Leopard, even keystroke events generate SystemDefined events, but
578      their subtype is 8. */
579   if ([event type] != NSSystemDefined || [event subtype] == 8
580       /* Also, don't try this if from an event picked up asynchronously,
581          as lots of lisp evaluation happens in ns_update_menubar. */
582       || handling_signal != 0)
583     return;
584 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
585   ns_update_menubar (frame, 1, self);
589 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
591   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
592       && FRAME_NS_VIEW (SELECTED_FRAME ()))
593     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
594   return YES;
598 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
599    into an accelerator string.  We are only able to display a single character
600    for an accelerator, together with an optional modifier combination.  (Under
601    Carbon more control was possible, but in Cocoa multi-char strings passed to
602    NSMenuItem get ignored.  For now we try to display a super-single letter
603    combo, and return the others as strings to be appended to the item title.
604    (This is signaled by setting keyEquivModMask to 0 for now.) */
605 -(NSString *)parseKeyEquiv: (char *)key
607   char *tpos = key;
608   keyEquivModMask = NSCommandKeyMask;
610   if (!key || !strlen (key))
611     return @"";
612   
613   while (*tpos == ' ' || *tpos == '(')
614     tpos++;
615   if ((*tpos == 's') && (*(tpos+1) == '-'))
616     {
617       return [NSString stringWithFormat: @"%c", tpos[2]];
618     }
619   keyEquivModMask = 0; /* signal */
620   return [NSString stringWithUTF8String: tpos];
624 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
626   NSMenuItem *item;
627   widget_value *wv = (widget_value *)wvptr;
629   if (name_is_separator (wv->name))
630     {
631       item = [NSMenuItem separatorItem];
632       [self addItem: item];
633     }
634   else
635     {
636       NSString *title, *keyEq;
637       title = [NSString stringWithUTF8String: wv->name];
638       if (title == nil)
639         title = @"< ? >";  /* (get out in the open so we know about it) */
641       keyEq = [self parseKeyEquiv: wv->key];
642 #ifdef NS_IMPL_COCOA
643       /* OS X just ignores modifier strings longer than one character */
644       if (keyEquivModMask == 0)
645         title = [title stringByAppendingFormat: @" (%@)", keyEq];
646 #endif
648       item = [self addItemWithTitle: (NSString *)title
649                              action: @selector (menuDown:)
650                       keyEquivalent: keyEq];
651       [item setKeyEquivalentModifierMask: keyEquivModMask];
653       [item setEnabled: wv->enabled];
655       /* Draw radio buttons and tickboxes */
656       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
657                            wv->button_type == BUTTON_TYPE_RADIO))
658         [item setState: NSOnState];
659       else
660         [item setState: NSOffState];
662       [item setTag: (NSInteger)wv->call_data];
663     }
665   return item;
669 /* convenience */
670 -(void)clear
672   int n;
673   
674   for (n = [self numberOfItems]-1; n >= 0; n--)
675     {
676       NSMenuItem *item = [self itemAtIndex: n];
677       NSString *title = [item title];
678       if (([title length] == 0  /* OSX 10.5 */
679            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
680            || [@"Apple" isEqualToString: title]) /* older */
681           && ![item isSeparatorItem])
682         continue;
683       [self removeItemAtIndex: n];
684     }
688 - (void)fillWithWidgetValue: (void *)wvptr
690   widget_value *wv = (widget_value *)wvptr;
692   /* clear existing contents */
693   [self setMenuChangedMessagesEnabled: NO];
694   [self clear];
696   /* add new contents */
697   for (; wv != NULL; wv = wv->next)
698     {
699       NSMenuItem *item = [self addItemWithWidgetValue: wv];
701       if (wv->contents)
702         {
703           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
705           [self setSubmenu: submenu forItem: item];
706           [submenu fillWithWidgetValue: wv->contents];
707           [submenu release];
708           [item setAction: nil];
709         }
710     }
712   [self setMenuChangedMessagesEnabled: YES];
713 #ifdef NS_IMPL_GNUSTEP
714   if ([[self window] isVisible])
715     [self sizeToFit];
716 #else
717   if ([self supermenu] == nil)
718     [self sizeToFit];
719 #endif
723 /* adds an empty submenu and returns it */
724 - (EmacsMenu *)addSubmenuWithTitle: (char *)title forFrame: (struct frame *)f
726   NSString *titleStr = [NSString stringWithUTF8String: title];
727   NSMenuItem *item = [self addItemWithTitle: titleStr
728                                      action: nil /*@selector (menuDown:) */
729                               keyEquivalent: @""];
730   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
731   [self setSubmenu: submenu forItem: item];
732   [submenu release];
733   return submenu;
736 /* run a menu in popup mode */
737 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
738                  keymaps: (int)keymaps
740   EmacsView *view = FRAME_NS_VIEW (f);
741 /*   p = [view convertPoint:p fromView: nil]; */
742   p.y = NSHeight ([view frame]) - p.y;
743   NSEvent *e = [[view window] currentEvent];
744   NSEvent *event = [NSEvent mouseEventWithType: NSRightMouseDown
745                                       location: p
746                                  modifierFlags: 0
747                                      timestamp: [e timestamp]
748                                   windowNumber: [[view window] windowNumber]
749                                        context: [e context]
750                                    eventNumber: 0/*[e eventNumber] */
751                                     clickCount: 1
752                                       pressure: 0];
753   long retVal;
755   context_menu_value = -1;
756   [NSMenu popUpContextMenu: self withEvent: event forView: view];
757   retVal = context_menu_value;
758   context_menu_value = 0;
759   return retVal > 0
760       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
761       : Qnil;
764 @end  /* EmacsMenu */
768 /* ==========================================================================
770     Context Menu: implementing functions
772    ========================================================================== */
774 static Lisp_Object
775 cleanup_popup_menu (Lisp_Object arg)
777   discard_menu_items ();
778   return Qnil;
782 static Lisp_Object
783 ns_popup_menu (Lisp_Object position, Lisp_Object menu)
785   EmacsMenu *pmenu;
786   struct frame *f = NULL;
787   NSPoint p;
788   Lisp_Object window, x, y, tem, keymap, title;
789   struct gcpro gcpro1;
790   int specpdl_count = SPECPDL_INDEX (), specpdl_count2;
791   char *error_name = NULL;
792   int keymaps = 0;
793   widget_value *wv, *first_wv = 0;
795   NSTRACE (ns_popup_menu);
797   if (!NILP (position))
798     {
799       check_ns ();
800   
801       if (EQ (position, Qt)
802           || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
803                                    || EQ (XCAR (position), Qtool_bar))))
804         {
805           /* Use the mouse's current position.  */
806           struct frame *new_f = SELECTED_FRAME ();
808           if (FRAME_TERMINAL (new_f)->mouse_position_hook)
809             (*FRAME_TERMINAL (new_f)->mouse_position_hook)
810               (&new_f, 0, 0, 0, &x, &y, 0);
811           if (new_f != 0)
812             XSETFRAME (window, new_f);
813           else
814             {
815               window = selected_window;
816               x = make_number (0);
817               y = make_number (0);
818             }
819         }
820       else
821         {
822           CHECK_CONS (position);
823           tem = Fcar (position);
824           if (XTYPE (tem) == Lisp_Cons)
825             {
826               window = Fcar (Fcdr (position));
827               x = Fcar (tem);
828               y = Fcar (Fcdr (tem));
829             }
830           else
831             {
832               tem = Fcar (Fcdr (position));
833               window = Fcar (tem);
834               tem = Fcar (Fcdr (Fcdr (tem)));
835               x = Fcar (tem);
836               y = Fcdr (tem);
837             }
838         }
839   
840       CHECK_NUMBER (x);
841       CHECK_NUMBER (y);
843       if (FRAMEP (window))
844         {
845           f = XFRAME (window);
846       
847           p.x = 0;
848           p.y = 0;
849         }
850       else
851         {
852           struct window *win = XWINDOW (window);
853           CHECK_LIVE_WINDOW (window);
854           f = XFRAME (WINDOW_FRAME (win));
855           p.x = FRAME_COLUMN_WIDTH (f) * WINDOW_LEFT_EDGE_COL (win);
856           p.y = FRAME_LINE_HEIGHT (f) * WINDOW_TOP_EDGE_LINE (win);
857         }
859       p.x += XINT (x); p.y += XINT (y);
861       XSETFRAME (Vmenu_updating_frame, f);
862     }
863   else
864     {      /* no position given */
865       /* FIXME: if called during dump, we need to stop precomputation of
866          key equivalents (see below) because the keydefs in ns-win.el have
867          not been loaded yet. */
868       if (noninteractive)
869         return Qnil;
870       Vmenu_updating_frame = Qnil;
871     }
873   /* now parse the lisp menus */
874   record_unwind_protect (unuse_menu_items, Qnil);
875   title = Qnil;
876   GCPRO1 (title);
878   /* Decode the menu items from what was specified.  */
880   keymap = get_keymap (menu, 0, 0);
881   if (CONSP (keymap))
882     {
883       /* We were given a keymap.  Extract menu info from the keymap.  */
884       Lisp_Object prompt;
886       /* Extract the detailed info to make one pane.  */
887       keymap_panes (&menu, 1, NILP (position));
889       /* Search for a string appearing directly as an element of the keymap.
890          That string is the title of the menu.  */
891       prompt = Fkeymap_prompt (keymap);
892       title = NILP (prompt) ? build_string ("Select") : prompt;
894       /* Make that be the pane title of the first pane.  */
895       if (!NILP (prompt) && menu_items_n_panes >= 0)
896         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt;
898       keymaps = 1;
899     }
900   else if (CONSP (menu) && KEYMAPP (XCAR (menu)))
901     {
902       /* We were given a list of keymaps.  */
903       int nmaps = XFASTINT (Flength (menu));
904       Lisp_Object *maps
905         = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object));
906       int i;
908       title = Qnil;
910       /* The first keymap that has a prompt string
911          supplies the menu title.  */
912       for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem))
913         {
914           Lisp_Object prompt;
916           maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0);
918           prompt = Fkeymap_prompt (keymap);
919           if (NILP (title) && !NILP (prompt))
920             title = prompt;
921         }
923       /* Extract the detailed info to make one pane.  */
924       keymap_panes (maps, nmaps, NILP (position));
926       /* Make the title be the pane title of the first pane.  */
927       if (!NILP (title) && menu_items_n_panes >= 0)
928         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title;
930       keymaps = 1;
931     }
932   else
933     {
934       /* We were given an old-fashioned menu.  */
935       title = Fcar (menu);
936       CHECK_STRING (title);
938       list_of_panes (Fcdr (menu));
940       keymaps = 0;
941     }
943   unbind_to (specpdl_count, Qnil);
945   /* If no position given, that was a signal to just precompute and cache
946      key equivalents, which was a side-effect of what we just did. */
947   if (NILP (position))
948     {
949       discard_menu_items ();
950       UNGCPRO;
951       return Qnil;
952     }
954   record_unwind_protect (cleanup_popup_menu, Qnil);
955   BLOCK_INPUT;
957   /* now parse stage 2 as in ns_update_menubar */
958   wv = xmalloc_widget_value ();
959   wv->name = "contextmenu";
960   wv->value = 0;
961   wv->enabled = 1;
962   wv->button_type = BUTTON_TYPE_NONE;
963   wv->help = Qnil;
964   first_wv = wv;
966   specpdl_count2 = SPECPDL_INDEX ();
968 #if 0
969   /* FIXME: a couple of one-line differences prevent reuse */
970   wv = digest_single_submenu (0, menu_items_used, Qnil);
971 #else
972   {
973   widget_value *save_wv = 0, *prev_wv = 0;
974   widget_value **submenu_stack
975     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
976 /*   Lisp_Object *subprefix_stack
977        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
978   int submenu_depth = 0;
979   int first_pane = 1;
980   int i;
982   /* Loop over all panes and items, filling in the tree.  */
983   i = 0;
984   while (i < menu_items_used)
985     {
986       if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
987         {
988           submenu_stack[submenu_depth++] = save_wv;
989           save_wv = prev_wv;
990           prev_wv = 0;
991           first_pane = 1;
992           i++;
993         }
994       else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
995         {
996           prev_wv = save_wv;
997           save_wv = submenu_stack[--submenu_depth];
998           first_pane = 0;
999           i++;
1000         }
1001       else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
1002                && submenu_depth != 0)
1003         i += MENU_ITEMS_PANE_LENGTH;
1004       /* Ignore a nil in the item list.
1005          It's meaningful only for dialog boxes.  */
1006       else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
1007         i += 1;
1008       else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
1009         {
1010           /* Create a new pane.  */
1011           Lisp_Object pane_name, prefix;
1012           char *pane_string;
1014           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
1015           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
1017 #ifndef HAVE_MULTILINGUAL_MENU
1018           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
1019             {
1020               pane_name = ENCODE_MENU_STRING (pane_name);
1021               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
1022             }
1023 #endif
1024           pane_string = (NILP (pane_name)
1025                          ? "" : (char *) SDATA (pane_name));
1026           /* If there is just one top-level pane, put all its items directly
1027              under the top-level menu.  */
1028           if (menu_items_n_panes == 1)
1029             pane_string = "";
1031           /* If the pane has a meaningful name,
1032              make the pane a top-level menu item
1033              with its items as a submenu beneath it.  */
1034           if (!keymaps && strcmp (pane_string, ""))
1035             {
1036               wv = xmalloc_widget_value ();
1037               if (save_wv)
1038                 save_wv->next = wv;
1039               else
1040                 first_wv->contents = wv;
1041               wv->name = pane_string;
1042               if (keymaps && !NILP (prefix))
1043                 wv->name++;
1044               wv->value = 0;
1045               wv->enabled = 1;
1046               wv->button_type = BUTTON_TYPE_NONE;
1047               wv->help = Qnil;
1048               save_wv = wv;
1049               prev_wv = 0;
1050             }
1051           else if (first_pane)
1052             {
1053               save_wv = wv;
1054               prev_wv = 0;
1055             }
1056           first_pane = 0;
1057           i += MENU_ITEMS_PANE_LENGTH;
1058         }
1059       else
1060         {
1061           /* Create a new item within current pane.  */
1062           Lisp_Object item_name, enable, descrip, def, type, selected, help;
1063           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
1064           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
1065           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
1066           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
1067           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
1068           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
1069           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
1071 #ifndef HAVE_MULTILINGUAL_MENU
1072           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
1073             {
1074               item_name = ENCODE_MENU_STRING (item_name);
1075               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
1076             }
1078           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
1079             {
1080               descrip = ENCODE_MENU_STRING (descrip);
1081               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
1082             }
1083 #endif /* not HAVE_MULTILINGUAL_MENU */
1085           wv = xmalloc_widget_value ();
1086           if (prev_wv)
1087             prev_wv->next = wv;
1088           else
1089             save_wv->contents = wv;
1090           wv->name = (char *) SDATA (item_name);
1091           if (!NILP (descrip))
1092             wv->key = (char *) SDATA (descrip);
1093           wv->value = 0;
1094           /* If this item has a null value,
1095              make the call_data null so that it won't display a box
1096              when the mouse is on it.  */
1097           wv->call_data
1098               = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
1099           wv->enabled = !NILP (enable);
1101           if (NILP (type))
1102             wv->button_type = BUTTON_TYPE_NONE;
1103           else if (EQ (type, QCtoggle))
1104             wv->button_type = BUTTON_TYPE_TOGGLE;
1105           else if (EQ (type, QCradio))
1106             wv->button_type = BUTTON_TYPE_RADIO;
1107           else
1108             abort ();
1110           wv->selected = !NILP (selected);
1112           if (! STRINGP (help))
1113             help = Qnil;
1115           wv->help = help;
1117           prev_wv = wv;
1119           i += MENU_ITEMS_ITEM_LENGTH;
1120         }
1121     }
1122   }
1123 #endif
1125   if (!NILP (title))
1126     {
1127       widget_value *wv_title = xmalloc_widget_value ();
1128       widget_value *wv_sep = xmalloc_widget_value ();
1130       /* Maybe replace this separator with a bitmap or owner-draw item
1131          so that it looks better.  Having two separators looks odd.  */
1132       wv_sep->name = "--";
1133       wv_sep->next = first_wv->contents;
1134       wv_sep->help = Qnil;
1136 #ifndef HAVE_MULTILINGUAL_MENU
1137       if (STRING_MULTIBYTE (title))
1138         title = ENCODE_MENU_STRING (title);
1139 #endif
1141       wv_title->name = (char *) SDATA (title);
1142       wv_title->enabled = NO;
1143       wv_title->button_type = BUTTON_TYPE_NONE;
1144       wv_title->help = Qnil;
1145       wv_title->next = wv_sep;
1146       first_wv->contents = wv_title;
1147     }
1149   pmenu = [[EmacsMenu alloc] initWithTitle:
1150                                [NSString stringWithUTF8String: SDATA (title)]];
1151   [pmenu fillWithWidgetValue: first_wv->contents];
1152   free_menubar_widget_value_tree (first_wv);
1153   unbind_to (specpdl_count2, Qnil);
1155   popup_activated_flag = 1;
1156   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1157   popup_activated_flag = 0;
1158   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1160   UNBLOCK_INPUT;
1161   discard_menu_items ();
1162   unbind_to (specpdl_count, Qnil);
1163   UNGCPRO;
1165   if (error_name) error (error_name);
1166   return tem;
1172 /* ==========================================================================
1174     Toolbar: externally-called functions
1176    ========================================================================== */
1178 void
1179 free_frame_tool_bar (FRAME_PTR f)
1180 /* --------------------------------------------------------------------------
1181     Under NS we just hide the toolbar until it might be needed again.
1182    -------------------------------------------------------------------------- */
1184   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1187 void
1188 update_frame_tool_bar (FRAME_PTR f)
1189 /* --------------------------------------------------------------------------
1190     Update toolbar contents
1191    -------------------------------------------------------------------------- */
1193   int i;
1194   EmacsToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar];
1196   [toolbar clearActive];
1198   /* update EmacsToolbar as in GtkUtils, build items list */
1199   for (i = 0; i < f->n_tool_bar_items; ++i)
1200     {
1201 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1202                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1204       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1205       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1206       int idx;
1207       int img_id;
1208       struct image *img;
1209       Lisp_Object image;
1210       Lisp_Object helpObj;
1211       char *helpText;
1213       /* If image is a vector, choose the image according to the
1214          button state.  */
1215       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1216       if (VECTORP (image))
1217         {
1218           /* NS toolbar auto-computes disabled and selected images */
1219           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1220           xassert (ASIZE (image) >= idx);
1221           image = AREF (image, idx);
1222         }
1223       else
1224         {
1225           idx = -1;
1226         }
1227       /* Ignore invalid image specifications.  */
1228       if (!valid_image_p (image))
1229         {
1230           NSLog (@"Invalid image for toolbar item");
1231           continue;
1232         }
1234       img_id = lookup_image (f, image);
1235       img = IMAGE_FROM_ID (f, img_id);
1236       prepare_image_for_display (f, img);
1238       if (img->load_failed_p || img->pixmap == nil)
1239         {
1240           NSLog (@"Could not prepare toolbar image for display.");
1241           continue;
1242         }
1244       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1245       if (NILP (helpObj))
1246         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1247       helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1249       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1250                                enabled: enabled_p];
1251 #undef TOOLPROP
1252     }
1254   if (![toolbar isVisible])
1255       [toolbar setVisible: YES];
1257   if ([toolbar changed])
1258     {
1259       /* inform app that toolbar has changed */
1260       NSDictionary *dict = [toolbar configurationDictionary];
1261       NSMutableDictionary *newDict = [dict mutableCopy];
1262       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1263       NSObject *key;
1264       while ((key = [keys nextObject]) != nil)
1265         {
1266           NSObject *val = [dict objectForKey: key];
1267           if ([val isKindOfClass: [NSArray class]])
1268             {
1269               [newDict setObject:
1270                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1271                           forKey: key];
1272               break;
1273             }
1274         }
1275       [toolbar setConfigurationFromDictionary: newDict];
1276       [newDict release];
1277     }
1282 /* ==========================================================================
1284     Toolbar: class implementation
1286    ========================================================================== */
1288 @implementation EmacsToolbar
1290 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1292   self = [super initWithIdentifier: identifier];
1293   emacsView = view;
1294   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1295   [self setSizeMode: NSToolbarSizeModeSmall];
1296   [self setDelegate: self];
1297   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1298   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1299   prevEnablement = enablement = 0L;
1300   return self;
1303 - (void)dealloc
1305   [prevIdentifiers release];
1306   [activeIdentifiers release];
1307   [identifierToItem release];
1308   [super dealloc];
1311 - (void) clearActive
1313   [prevIdentifiers release];
1314   prevIdentifiers = [activeIdentifiers copy];
1315   [activeIdentifiers removeAllObjects];
1316   prevEnablement = enablement;
1317   enablement = 0L;
1320 - (BOOL) changed
1322   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1323     enablement == prevEnablement ? NO : YES;
1326 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1327                         helpText: (char *)help enabled: (BOOL)enabled
1329   /* 1) come up w/identifier */
1330   NSString *identifier
1331       = [NSString stringWithFormat: @"%u", [img hash]];
1333   /* 2) create / reuse item */
1334   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1335   if (item == nil)
1336     {
1337       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1338                autorelease];
1339       [item setImage: img];
1340       [item setToolTip: [NSString stringWithUTF8String: help]];
1341       [item setTarget: emacsView];
1342       [item setAction: @selector (toolbarClicked:)];
1343     }
1345   [item setTag: idx];
1346   [item setEnabled: enabled];
1348   /* 3) update state */
1349   [identifierToItem setObject: item forKey: identifier];
1350   [activeIdentifiers addObject: identifier];
1351   enablement = (enablement << 1) | (enabled == YES);
1354 /* This overrides super's implementation, which automatically sets
1355    all items to enabled state (for some reason). */
1356 - (void)validateVisibleItems { }
1359 /* delegate methods */
1361 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1362       itemForItemIdentifier: (NSString *)itemIdentifier
1363   willBeInsertedIntoToolbar: (BOOL)flag
1365   /* look up NSToolbarItem by identifier and return... */
1366   return [identifierToItem objectForKey: itemIdentifier];
1369 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1371   /* return entire set.. */
1372   return activeIdentifiers;
1375 /* for configuration palette (not yet supported) */
1376 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1378   /* return entire set... */
1379   return [identifierToItem allKeys];
1382 /* optional and unneeded */
1383 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1384 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1385 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1387 @end  /* EmacsToolbar */
1391 /* ==========================================================================
1393     Tooltip: class implementation
1395    ========================================================================== */
1397 /* Needed because NeXTstep does not provide enough control over tooltip
1398    display. */
1399 @implementation EmacsTooltip
1401 - init
1403   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1404                                             blue: 0.792 alpha: 0.95];
1405   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1406   NSFont *sfont = [font screenFont];
1407   int height = [sfont ascender] - [sfont descender];
1408 /*[font boundingRectForFont].size.height; */
1409   NSRect r = NSMakeRect (0, 0, 100, height+6);
1411   textField = [[NSTextField alloc] initWithFrame: r];
1412   [textField setFont: font];
1413   [textField setBackgroundColor: col];
1415   [textField setEditable: NO];
1416   [textField setSelectable: NO];
1417   [textField setBordered: YES];
1418   [textField setBezeled: YES];
1419   [textField setDrawsBackground: YES];
1421   win = [[NSWindow alloc]
1422             initWithContentRect: [textField frame]
1423                       styleMask: 0
1424                         backing: NSBackingStoreBuffered
1425                           defer: YES];
1426   [win setReleasedWhenClosed: NO];
1427   [win setDelegate: self];
1428   [[win contentView] addSubview: textField];
1429 /*  [win setBackgroundColor: col]; */
1430   [win setOpaque: NO];
1432   return self;
1435 - (void) dealloc
1437   [win close];
1438   [win release];
1439   [textField release];
1440   [super dealloc];
1443 - (void) setText: (char *)text
1445   NSString *str = [NSString stringWithUTF8String: text];
1446   NSRect r = [textField frame];
1447   NSSize textSize = [str sizeWithAttributes: 
1448      [NSDictionary dictionaryWithObject: [[textField font] screenFont]
1449                                  forKey: NSFontAttributeName]];
1450   NSSize padSize = [[[textField font] screenFont] 
1451                      boundingRectForFont].size;
1453   r.size.width = textSize.width + padSize.width/2;
1454   r.size.height = textSize.height + padSize.height/2;
1455   [textField setFrame: r];
1456   [textField setStringValue: str];
1459 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1461   NSRect wr = [win frame];
1463   wr.origin = NSMakePoint (x, y);
1464   wr.size = [textField frame].size;
1466   [win setFrame: wr display: YES];
1467   [win orderFront: self];
1468   [win display];
1469   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1470                                          selector: @selector (hide)
1471                                          userInfo: nil repeats: NO];
1472   [timer retain];
1475 - (void) hide
1477   [win close];
1478   if (timer != nil)
1479     {
1480       if ([timer isValid])
1481         [timer invalidate];
1482       [timer release];
1483       timer = nil;
1484     }
1487 - (BOOL) isActive
1489   return timer != nil;
1492 - (NSRect) frame
1494   return [textField frame];
1497 @end  /* EmacsTooltip */
1501 /* ==========================================================================
1503     Popup Dialog: implementing functions
1505    ========================================================================== */
1508 static Lisp_Object
1509 pop_down_menu (Lisp_Object arg)
1511   struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1512   if (popup_activated_flag)
1513     {
1514       popup_activated_flag = 0;
1515       BLOCK_INPUT;
1516       [NSApp endModalSession: popupSession];
1517       [((EmacsDialogPanel *) (p->pointer)) close];
1518       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1519       UNBLOCK_INPUT;
1520     }
1521   return Qnil;
1525 Lisp_Object
1526 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1528   id dialog;
1529   Lisp_Object window, tem;
1530   struct frame *f;
1531   NSPoint p;
1532   BOOL isQ;
1534   NSTRACE (x-popup-dialog);
1535   
1536   check_ns ();
1538   isQ = NILP (header);
1540   if (EQ (position, Qt)
1541       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1542                                || EQ (XCAR (position), Qtool_bar))))
1543     {
1544       window = selected_window;
1545     }
1546   else if (CONSP (position))
1547     {
1548       Lisp_Object tem;
1549       tem = Fcar (position);
1550       if (XTYPE (tem) == Lisp_Cons)
1551         window = Fcar (Fcdr (position));
1552       else
1553         {
1554           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1555           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1556         }
1557     }
1558   else if (WINDOWP (position) || FRAMEP (position))
1559     {
1560       window = position;
1561     }
1562   else
1563     window = Qnil;
1565   if (FRAMEP (window))
1566     f = XFRAME (window);
1567   else if (WINDOWP (window))
1568     {
1569       CHECK_LIVE_WINDOW (window);
1570       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1571     }
1572   else
1573     CHECK_WINDOW (window);
1575   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1576   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1578   BLOCK_INPUT;
1579   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1580                                            isQuestion: isQ];
1581   {
1582     int specpdl_count = SPECPDL_INDEX ();
1583     record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1584     popup_activated_flag = 1;
1585     tem = [dialog runDialogAt: p];
1586     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1587   }
1588   UNBLOCK_INPUT;
1590   return tem;
1594 /* ==========================================================================
1596     Popup Dialog: class implementation
1598    ========================================================================== */
1600 @interface FlippedView : NSView
1603 @end
1605 @implementation FlippedView
1606 - (BOOL)isFlipped
1608   return YES;
1610 @end
1612 @implementation EmacsDialogPanel
1614 #define SPACER          8.0
1615 #define ICONSIZE        64.0
1616 #define TEXTHEIGHT      20.0
1617 #define MINCELLWIDTH    90.0
1619 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1620               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1622   NSSize spacing = {SPACER, SPACER};
1623   NSRect area;
1624   char this_cmd_name[80];
1625   id cell;
1626   static NSImageView *imgView;
1627   static FlippedView *contentView;
1629   if (imgView == nil)
1630     {
1631       NSImage *img;
1632       area.origin.x   = 3*SPACER;
1633       area.origin.y   = 2*SPACER;
1634       area.size.width = ICONSIZE;
1635       area.size.height= ICONSIZE;
1636       img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1637       [img setScalesWhenResized: YES];
1638       [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1639       imgView = [[NSImageView alloc] initWithFrame: area];
1640       [imgView setImage: img];
1641       [imgView setEditable: NO];
1642       [img release];
1643     }
1645   aStyle = NSTitledWindowMask;
1646   flag = YES;
1647   rows = 0;
1648   cols = 1;
1649   [super initWithContentRect: contentRect styleMask: aStyle
1650                      backing: backingType defer: flag];
1651   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1652   [self setContentView: contentView];
1654   [[self contentView] setAutoresizesSubviews: YES];
1656   [[self contentView] addSubview: imgView];
1657   [self setTitle: @""];
1659   area.origin.x   += ICONSIZE+2*SPACER;
1660 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1661   area.size.width = 400;
1662   area.size.height= TEXTHEIGHT;
1663   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1664   [[self contentView] addSubview: command];
1665   [command setStringValue: ns_app_name];
1666   [command setDrawsBackground: NO];
1667   [command setBezeled: NO];
1668   [command setSelectable: NO];
1669   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1671 /*  area.origin.x   = ICONSIZE+2*SPACER;
1672   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1673   area.size.width = 400;
1674   area.size.height= 2;
1675   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1676   [[self contentView] addSubview: tem];
1677   [tem setTitlePosition: NSNoTitle];
1678   [tem setAutoresizingMask: NSViewWidthSizable];*/
1680 /*  area.origin.x = ICONSIZE+2*SPACER; */
1681   area.origin.y += TEXTHEIGHT+SPACER;
1682   area.size.width = 400;
1683   area.size.height= TEXTHEIGHT;
1684   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1685   [[self contentView] addSubview: title];
1686   [title setDrawsBackground: NO];
1687   [title setBezeled: NO];
1688   [title setSelectable: NO];
1689   [title setFont: [NSFont systemFontOfSize: 11.0]];
1691   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1692   [cell setBordered: NO];
1693   [cell setEnabled: NO];
1694   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1695   [cell setBezelStyle: NSRoundedBezelStyle];
1697   matrix = [[NSMatrix alloc] initWithFrame: contentRect 
1698                                       mode: NSHighlightModeMatrix 
1699                                  prototype: cell 
1700                               numberOfRows: 0 
1701                            numberOfColumns: 1];
1702   [[self contentView] addSubview: matrix];
1703   [matrix release];
1704   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1705                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1706   [matrix setIntercellSpacing: spacing];
1708   [self setOneShot: YES];
1709   [self setReleasedWhenClosed: YES];
1710   [self setHidesOnDeactivate: YES];
1711   return self;
1715 - (BOOL)windowShouldClose: (id)sender
1717   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1718   return NO;
1722 void process_dialog (id window, Lisp_Object list)
1724   Lisp_Object item;
1725   int row = 0;
1727   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1728     {
1729       item = XCAR (list);
1730       if (XTYPE (item) == Lisp_String)
1731         {
1732           [window addString: SDATA (item) row: row++];
1733         }
1734       else if (XTYPE (item) == Lisp_Cons)
1735         {
1736           [window addButton: SDATA (XCAR (item))
1737                       value: XCDR (item) row: row++];
1738         }
1739       else if (NILP (item))
1740         {
1741           [window addSplit];
1742           row = 0;
1743         }
1744     }
1748 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1750   id cell;
1751        
1752   if (row >= rows)
1753     {
1754       [matrix addRow];
1755       rows++;
1756     }
1757   cell = [matrix cellAtRow: row column: cols-1];
1758   [cell setTarget: self];
1759   [cell setAction: @selector (clicked: )];
1760   [cell setTitle: [NSString stringWithUTF8String: str]];
1761   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1762   [cell setBordered: YES];
1763   [cell setEnabled: YES];
1765   return self;
1769 - addString: (char *)str row: (int)row
1771   id cell;
1772        
1773   if (row >= rows)
1774     {
1775       [matrix addRow];
1776       rows++;
1777     }
1778   cell = [matrix cellAtRow: row column: cols-1];
1779   [cell setTitle: [NSString stringWithUTF8String: str]];
1780   [cell setBordered: YES];
1781   [cell setEnabled: NO];
1783   return self;
1787 - addSplit
1789   [matrix addColumn];
1790   cols++;
1791   return self;
1795 - clicked: sender
1797   NSArray *sellist = nil;
1798   EMACS_INT seltag;
1800   sellist = [sender selectedCells];
1801   if ([sellist count]<1) 
1802     return self;
1804   seltag = [[sellist objectAtIndex: 0] tag];
1805   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1806     [NSApp stopModalWithCode: seltag];
1807   return self;
1811 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1813   Lisp_Object head;
1814   [super init];
1816   if (XTYPE (contents) == Lisp_Cons)
1817     {
1818       head = Fcar (contents);
1819       process_dialog (self, Fcdr (contents));
1820     }
1821   else
1822     head = contents;
1824   if (XTYPE (head) == Lisp_String)
1825       [title setStringValue:
1826                  [NSString stringWithUTF8String: SDATA (head)]];
1827   else if (isQ == YES)
1828       [title setStringValue: @"Question"];
1829   else
1830       [title setStringValue: @"Information"];
1832   {
1833     int i;
1834     NSRect r, s, t;
1836     if (cols == 1 && rows > 1)  /* Never told where to split */
1837       {
1838         [matrix addColumn];
1839         for (i = 0; i<rows/2; i++)
1840           {
1841             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1842                       atRow: i column: 1];
1843             [matrix removeRow: (rows+1)/2];
1844           }
1845       }
1847     [matrix sizeToFit];
1848     {
1849       NSSize csize = [matrix cellSize];
1850       if (csize.width < MINCELLWIDTH)
1851         {
1852           csize.width = MINCELLWIDTH;
1853           [matrix setCellSize: csize];
1854           [matrix sizeToCells];
1855         }
1856     }
1858     [title sizeToFit];
1859     [command sizeToFit];
1861     t = [matrix frame];
1862     r = [title frame];
1863     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1864       {
1865         t.origin.x   = r.origin.x;
1866         t.size.width = r.size.width;
1867       }
1868     r = [command frame];
1869     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1870       {
1871         t.origin.x   = r.origin.x;
1872         t.size.width = r.size.width;
1873       }
1875     r = [self frame];
1876     s = [(NSView *)[self contentView] frame];
1877     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1878     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1879     [self setFrame: r display: NO];
1880   }
1882   return self;
1886 - (void)dealloc
1888   { [super dealloc]; return; };
1892 - (Lisp_Object)runDialogAt: (NSPoint)p
1894   int ret;
1895   extern EMACS_TIME timer_check (int do_it_now); /* TODO: add to a header */
1897   /* initiate a session that will be ended by pop_down_menu */
1898   popupSession = [NSApp beginModalSessionForWindow: self];
1899   while (popup_activated_flag
1900          && (ret = [NSApp runModalSession: popupSession])
1901               == NSRunContinuesResponse)
1902     {
1903       /* Run this for timers.el, indep of atimers; might not return.
1904          TODO: use return value to avoid calling every iteration. */
1905       timer_check (1);
1906       [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1907     }
1909   {                             /* FIXME: BIG UGLY HACK!!! */
1910       Lisp_Object tmp;
1911       *(EMACS_INT*)(&tmp) = ret;
1912       return tmp;
1913   }
1916 @end
1919 /* ==========================================================================
1921     Lisp definitions
1923    ========================================================================== */
1925 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1926        doc: /* Cause the NS menu to be re-calculated.  */)
1927      ()
1929   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1930   return Qnil;
1934 DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
1935        doc: /* Pop up a deck-of-cards menu and return user's selection.
1936 POSITION is a position specification.  This is either a mouse button event
1937 or a list ((XOFFSET YOFFSET) WINDOW)
1938 where XOFFSET and YOFFSET are positions in pixels from the top left
1939 corner of WINDOW.  (WINDOW may be a window or a frame object.)
1940 This controls the position of the top left of the menu as a whole.
1941 If POSITION is t, it means to use the current mouse position.
1943 MENU is a specifier for a menu.  For the simplest case, MENU is a keymap.
1944 The menu items come from key bindings that have a menu string as well as
1945 a definition; actually, the \"definition\" in such a key binding looks like
1946 \(STRING . REAL-DEFINITION).  To give the menu a title, put a string into
1947 the keymap as a top-level element.
1949 If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.
1950 Otherwise, REAL-DEFINITION should be a valid key binding definition.
1952 You can also use a list of keymaps as MENU.
1953   Then each keymap makes a separate pane.
1955 When MENU is a keymap or a list of keymaps, the return value is the
1956 list of events corresponding to the user's choice. Note that
1957 `x-popup-menu' does not actually execute the command bound to that
1958 sequence of events.
1960 Alternatively, you can specify a menu of multiple panes
1961   with a list of the form (TITLE PANE1 PANE2...),
1962 where each pane is a list of form (TITLE ITEM1 ITEM2...).
1963 Each ITEM is normally a cons cell (STRING . VALUE);
1964 but a string can appear as an item--that makes a nonselectable line
1965 in the menu.
1966 With this form of menu, the return value is VALUE from the chosen item.  */)
1967      (position, menu)
1968      Lisp_Object position, menu;
1970   return ns_popup_menu (position, menu);
1974 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1975        doc: /* Pop up a dialog box and return user's selection.
1976 POSITION specifies which frame to use.
1977 This is normally a mouse button event or a window or frame.
1978 If POSITION is t, it means to use the frame the mouse is on.
1979 The dialog box appears in the middle of the specified frame.
1981 CONTENTS specifies the alternatives to display in the dialog box.
1982 It is a list of the form (DIALOG ITEM1 ITEM2...).
1983 Each ITEM is a cons cell (STRING . VALUE).
1984 The return value is VALUE from the chosen item.
1986 An ITEM may also be just a string--that makes a nonselectable item.
1987 An ITEM may also be nil--that means to put all preceding items
1988 on the left of the dialog box and all following items on the right.
1989 \(By default, approximately half appear on each side.)
1991 If HEADER is non-nil, the frame title for the box is "Information",
1992 otherwise it is "Question".
1994 If the user gets rid of the dialog box without making a valid choice,
1995 for instance using the window manager, then this produces a quit and
1996 `x-popup-dialog' does not return.  */)
1997      (position, contents, header)
1998      Lisp_Object position, contents, header;
2000   return ns_popup_dialog (position, contents, header);
2003 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
2004        doc: /* Return t if a menu or popup dialog is active.  */)
2005      ()
2007   return popup_activated () ? Qt : Qnil;
2010 /* ==========================================================================
2012     Lisp interface declaration
2014    ========================================================================== */
2016 void
2017 syms_of_nsmenu ()
2019   defsubr (&Sx_popup_menu);
2020   defsubr (&Sx_popup_dialog);
2021   defsubr (&Sns_reset_menu);
2022   defsubr (&Smenu_or_popup_active_p);
2023   staticpro (&menu_items);
2024   menu_items = Qnil;
2026   Qdebug_on_next_call = intern ("debug-on-next-call");
2027   staticpro (&Qdebug_on_next_call);
2030 // arch-tag: 75773656-52e5-4c44-a398-47bd87b32619