(get_next_display_element): Use an enum in last change.
[emacs.git] / src / nsmenu.m
blobf6603ce8c37c0c40583cdfe3393f5b649a231ae2
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: ns_app_name];
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 #ifdef NS_IMPL_GNUSTEP
175   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
176 #endif
178   if (deep_p)
179     {
180       /* Fully parse one or more of the submenus. */
181       int n = 0;
182       int *submenu_start, *submenu_end;
183       int *submenu_top_level_items, *submenu_n_panes;
184       struct buffer *prev = current_buffer;
185       Lisp_Object buffer;
186       int specpdl_count = SPECPDL_INDEX ();
187       int previous_menu_items_used = f->menu_bar_items_used;
188       Lisp_Object *previous_items
189         = (Lisp_Object *) alloca (previous_menu_items_used
190                                   * sizeof (Lisp_Object));
192       /* lisp preliminaries */
193       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
194       specbind (Qinhibit_quit, Qt);
195       specbind (Qdebug_on_next_call, Qnil);
196       record_unwind_save_match_data ();
197       if (NILP (Voverriding_local_map_menu_flag))
198         {
199           specbind (Qoverriding_terminal_local_map, Qnil);
200           specbind (Qoverriding_local_map, Qnil);
201         }
202       set_buffer_internal_1 (XBUFFER (buffer));
204       /* TODO: for some reason this is not needed in other terms,
205            but some menu updates call Info-extract-pointer which causes
206            abort-on-error if waiting-for-input.  Needs further investigation. */
207       owfi = waiting_for_input;
208       waiting_for_input = 0;
210       /* lucid hook and possible reset */
211       safe_run_hooks (Qactivate_menubar_hook);
212       if (! NILP (Vlucid_menu_bar_dirty_flag))
213         call0 (Qrecompute_lucid_menubar);
214       safe_run_hooks (Qmenu_bar_update_hook);
215       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
217       /* Now ready to go */
218       items = FRAME_MENU_BAR_ITEMS (f);
220       /* Save the frame's previous menu bar contents data */
221       if (previous_menu_items_used)
222         bcopy (XVECTOR (f->menu_bar_vector)->contents, previous_items,
223                previous_menu_items_used * sizeof (Lisp_Object));
225       /* parse stage 1: extract from lisp */
226       save_menu_items ();
228       menu_items = f->menu_bar_vector;
229       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
230       submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
231       submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
232       submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
233       submenu_top_level_items
234         = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
235       init_menu_items ();
236       for (i = 0; i < XVECTOR (items)->size; i += 4)
237         {
238           Lisp_Object key, string, maps;
240           key = XVECTOR (items)->contents[i];
241           string = XVECTOR (items)->contents[i + 1];
242           maps = XVECTOR (items)->contents[i + 2];
243           if (NILP (string))
244             break;
246           /* FIXME: we'd like to only parse the needed submenu, but this
247                was causing crashes in the _common parsing code.. need to make
248                sure proper initialization done.. */
249 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
250              continue; */
252           submenu_start[i] = menu_items_used;
254           menu_items_n_panes = 0;
255           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
256           submenu_n_panes[i] = menu_items_n_panes;
257           submenu_end[i] = menu_items_used;
258           n++;
259         }
261       finish_menu_items ();
262       waiting_for_input = owfi;
265       if (submenu && n == 0)
266         {
267           /* should have found a menu for this one but didn't */
268           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
269                   submenuTitle);
270           discard_menu_items ();
271           unbind_to (specpdl_count, Qnil);
272           [pool release];
273           UNBLOCK_INPUT;
274           return;
275         }
277       /* parse stage 2: insert into lucid 'widget_value' structures
278          [comments in other terms say not to evaluate lisp code here] */
279       wv = xmalloc_widget_value ();
280       wv->name = "menubar";
281       wv->value = 0;
282       wv->enabled = 1;
283       wv->button_type = BUTTON_TYPE_NONE;
284       wv->help = Qnil;
285       first_wv = wv;
287       for (i = 0; i < 4*n; i += 4)
288         {
289           menu_items_n_panes = submenu_n_panes[i];
290           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
291                                       submenu_top_level_items[i]);
292           if (prev_wv)
293             prev_wv->next = wv;
294           else
295             first_wv->contents = wv;
296           /* Don't set wv->name here; GC during the loop might relocate it.  */
297           wv->enabled = 1;
298           wv->button_type = BUTTON_TYPE_NONE;
299           prev_wv = wv;
300         }
302       set_buffer_internal_1 (prev);
304       /* Compare the new menu items with previous, and leave off if no change */
305       /* FIXME: following other terms here, but seems like this should be
306            done before parse stage 2 above, since its results aren't used */
307       if (previous_menu_items_used
308           && (!submenu || (submenu && submenu == last_submenu))
309           && menu_items_used == previous_menu_items_used)
310         {
311           for (i = 0; i < previous_menu_items_used; i++)
312             /* FIXME: this ALWAYS fails on Buffers menu items.. something
313                  about their strings causes them to change every time, so we
314                  double-check failures */
315             if (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i]))
316               if (!(STRINGP (previous_items[i])
317                     && STRINGP (XVECTOR (menu_items)->contents[i])
318                     && !strcmp (SDATA (previous_items[i]),
319                                SDATA (XVECTOR (menu_items)->contents[i]))))
320                   break;
321           if (i == previous_menu_items_used)
322             {
323               /* No change.. */
325 #if NSMENUPROFILE
326               ftime (&tb);
327               t += 1000*tb.time+tb.millitm;
328               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
329 #endif
331               free_menubar_widget_value_tree (first_wv);
332               discard_menu_items ();
333               unbind_to (specpdl_count, Qnil);
334               [pool release];
335               UNBLOCK_INPUT;
336               return;
337             }
338         }
339       /* The menu items are different, so store them in the frame */
340       /* FIXME: this is not correct for single-submenu case */
341       f->menu_bar_vector = menu_items;
342       f->menu_bar_items_used = menu_items_used;
344       /* Calls restore_menu_items, etc., as they were outside */
345       unbind_to (specpdl_count, Qnil);
347       /* Parse stage 2a: now GC cannot happen during the lifetime of the
348          widget_value, so it's safe to store data from a Lisp_String */
349       wv = first_wv->contents;
350       for (i = 0; i < XVECTOR (items)->size; i += 4)
351         {
352           Lisp_Object string;
353           string = XVECTOR (items)->contents[i + 1];
354           if (NILP (string))
355             break;
356 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
357                continue; */
359           wv->name = (char *) SDATA (string);
360           update_submenu_strings (wv->contents);
361           wv = wv->next;
362         }
364       /* Now, update the NS menu; if we have a submenu, use that, otherwise
365          create a new menu for each sub and fill it. */
366       if (submenu)
367         {
368           for (wv = first_wv->contents; wv; wv = wv->next)
369             {
370               if (!strcmp (submenuTitle, wv->name))
371                 {
372                   [submenu fillWithWidgetValue: wv->contents];
373                   last_submenu = submenu;
374                   break;
375                 }
376             }
377         }
378       else
379         {
380           [menu fillWithWidgetValue: first_wv->contents];
381         }
383     }
384   else
385     {
386       static int n_previous_strings = 0;
387       static char previous_strings[100][10];
388       static struct frame *last_f = NULL;
389       int n;
390       Lisp_Object string;
392       wv = xmalloc_widget_value ();
393       wv->name = "menubar";
394       wv->value = 0;
395       wv->enabled = 1;
396       wv->button_type = BUTTON_TYPE_NONE;
397       wv->help = Qnil;
398       first_wv = wv;
400       /* Make widget-value tree w/ just the top level menu bar strings */
401       items = FRAME_MENU_BAR_ITEMS (f);
402       if (NILP (items))
403         {
404           [pool release];
405           UNBLOCK_INPUT;
406           return;
407         }
410       /* check if no change.. this mechanism is a bit rough, but ready */
411       n = XVECTOR (items)->size / 4;
412       if (f == last_f && n_previous_strings == n)
413         {
414           for (i = 0; i<n; i++)
415             {
416               string = AREF (items, 4*i+1);
418               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
419                 continue;
420               if (NILP (string))
421                 if (previous_strings[i][0])
422                   break;
423               else
424                 continue;
425               if (strncmp (previous_strings[i], SDATA (string), 10))
426                 break;
427             }
429           if (i == n)
430             {
431               [pool release];
432               UNBLOCK_INPUT;
433               return;
434             }
435         }
437       [menu clear];
438       for (i = 0; i < XVECTOR (items)->size; i += 4)
439         {
440           string = XVECTOR (items)->contents[i + 1];
441           if (NILP (string))
442             break;
444           if (n < 100)
445             strncpy (previous_strings[i/4], SDATA (string), 10);
447           wv = xmalloc_widget_value ();
448           wv->name = (char *) SDATA (string);
449           wv->value = 0;
450           wv->enabled = 1;
451           wv->button_type = BUTTON_TYPE_NONE;
452           wv->help = Qnil;
453           wv->call_data = (void *) (EMACS_INT) (-1);
455 #ifdef NS_IMPL_COCOA
456           /* we'll update the real copy under app menu when time comes */
457           if (!strcmp ("Services", wv->name))
458             {
459               /* but we need to make sure it will update on demand */
460               [svcsMenu setFrame: f];
461               [svcsMenu setDelegate: svcsMenu];
462             }
463           else
464 #endif
465           [menu addSubmenuWithTitle: wv->name forFrame: f];
467           if (prev_wv)
468             prev_wv->next = wv;
469           else
470             first_wv->contents = wv;
471           prev_wv = wv;
472         }
474       last_f = f;
475       if (n < 100)
476         n_previous_strings = n;
477       else
478         n_previous_strings = 0;
480     }
481   free_menubar_widget_value_tree (first_wv);
484 #if NSMENUPROFILE
485   ftime (&tb);
486   t += 1000*tb.time+tb.millitm;
487   fprintf (stderr, "Menu update took %ld msec.\n", t);
488 #endif
490   /* set main menu */
491   if (needsSet)
492     [NSApp setMainMenu: menu];
494   [pool release];
495   UNBLOCK_INPUT;
500 /* Main emacs core entry point for menubar menus: called to indicate that the
501    frame's menus have changed, and the *step representation should be updated
502    from Lisp. */
503 void
504 set_frame_menubar (struct frame *f, int first_time, int deep_p)
506   ns_update_menubar (f, deep_p, nil);
510 /* Utility (from macmenu.c): is this item a separator? */
511 static int
512 name_is_separator (name)
513      const char *name;
515   const char *start = name;
517   /* Check if name string consists of only dashes ('-').  */
518   while (*name == '-') name++;
519   /* Separators can also be of the form "--:TripleSuperMegaEtched"
520      or "--deep-shadow".  We don't implement them yet, se we just treat
521      them like normal separators.  */
522   return (*name == '\0' || start + 2 == name);
526 /* ==========================================================================
528     Menu: class implementation
530    ========================================================================== */
533 /* Menu that can define itself from Emacs "widget_value"s and will lazily
534    update itself when user clicked.  Based on Carbon/AppKit implementation
535    by Yamamoto Mitsuharu. */
536 @implementation EmacsMenu
538 /* override designated initializer */
539 - initWithTitle: (NSString *)title
541   if (self = [super initWithTitle: title])
542     [self setAutoenablesItems: NO];
543   return self;
547 /* used for top-level */
548 - initWithTitle: (NSString *)title frame: (struct frame *)f
550   [self initWithTitle: title];
551   frame = f;
552 #ifdef NS_IMPL_COCOA
553   [self setDelegate: self];
554 #endif
555   return self;
559 - (void)setFrame: (struct frame *)f
561   frame = f;
565 /* delegate method called when a submenu is being opened: run a 'deep' call
566    to set_frame_menubar */
567 - (void)menuNeedsUpdate: (NSMenu *)menu
569   NSEvent *event;
570   if (!FRAME_LIVE_P (frame))
571     return;
572   event = [[FRAME_NS_VIEW (frame) window] currentEvent];
573   /* HACK: Cocoa/Carbon will request update on every keystroke
574      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
575      since key equivalents are handled through emacs.
576      On Leopard, even keystroke events generate SystemDefined events, but
577      their subtype is 8. */
578   if ([event type] != NSSystemDefined || [event subtype] == 8
579       /* Also, don't try this if from an event picked up asynchronously,
580          as lots of lisp evaluation happens in ns_update_menubar. */
581       || handling_signal != 0)
582     return;
583 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
584   ns_update_menubar (frame, 1, self);
588 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
590   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
591       && FRAME_NS_VIEW (SELECTED_FRAME ()))
592     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
593   return YES;
597 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
598    into an accelerator string.  We are only able to display a single character
599    for an accelerator, together with an optional modifier combination.  (Under
600    Carbon more control was possible, but in Cocoa multi-char strings passed to
601    NSMenuItem get ignored.  For now we try to display a super-single letter
602    combo, and return the others as strings to be appended to the item title.
603    (This is signaled by setting keyEquivModMask to 0 for now.) */
604 -(NSString *)parseKeyEquiv: (char *)key
606   char *tpos = key;
607   keyEquivModMask = NSCommandKeyMask;
609   if (!key || !strlen (key))
610     return @"";
611   
612   while (*tpos == ' ' || *tpos == '(')
613     tpos++;
614   if ((*tpos == 's') && (*(tpos+1) == '-'))
615     {
616       return [NSString stringWithFormat: @"%c", tpos[2]];
617     }
618   keyEquivModMask = 0; /* signal */
619   return [NSString stringWithUTF8String: tpos];
623 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
625   NSMenuItem *item;
626   widget_value *wv = (widget_value *)wvptr;
628   if (name_is_separator (wv->name))
629     {
630       item = [NSMenuItem separatorItem];
631       [self addItem: item];
632     }
633   else
634     {
635       NSString *title, *keyEq;
636       title = [NSString stringWithUTF8String: wv->name];
637       if (title == nil)
638         title = @"< ? >";  /* (get out in the open so we know about it) */
640       keyEq = [self parseKeyEquiv: wv->key];
641 #ifdef NS_IMPL_COCOA
642       /* OS X just ignores modifier strings longer than one character */
643       if (keyEquivModMask == 0)
644         title = [title stringByAppendingFormat: @" (%@)", keyEq];
645 #endif
647       item = [self addItemWithTitle: (NSString *)title
648                              action: @selector (menuDown:)
649                       keyEquivalent: keyEq];
650       [item setKeyEquivalentModifierMask: keyEquivModMask];
652       [item setEnabled: wv->enabled];
654       /* Draw radio buttons and tickboxes */
655       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
656                            wv->button_type == BUTTON_TYPE_RADIO))
657         [item setState: NSOnState];
658       else
659         [item setState: NSOffState];
661       [item setTag: (int)wv->call_data];
662     }
664   return item;
668 /* convenience */
669 -(void)clear
671   int n;
672   
673   for (n = [self numberOfItems]-1; n >= 0; n--)
674     {
675       NSMenuItem *item = [self itemAtIndex: n];
676       NSString *title = [item title];
677       if (([title length] == 0  /* OSX 10.5 */
678            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
679            || [@"Apple" isEqualToString: title]) /* older */
680           && ![item isSeparatorItem])
681         continue;
682       [self removeItemAtIndex: n];
683     }
687 - (void)fillWithWidgetValue: (void *)wvptr
689   widget_value *wv = (widget_value *)wvptr;
691   /* clear existing contents */
692   [self setMenuChangedMessagesEnabled: NO];
693   [self clear];
695   /* add new contents */
696   for (; wv != NULL; wv = wv->next)
697     {
698       NSMenuItem *item = [self addItemWithWidgetValue: wv];
700       if (wv->contents)
701         {
702           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
704           [self setSubmenu: submenu forItem: item];
705           [submenu fillWithWidgetValue: wv->contents];
706           [submenu release];
707           [item setAction: nil];
708         }
709     }
711   [self setMenuChangedMessagesEnabled: YES];
712 #ifdef NS_IMPL_GNUSTEP
713   if ([[self window] isVisible])
714     [self sizeToFit];
715 #else
716   if ([self supermenu] == nil)
717     [self sizeToFit];
718 #endif
722 /* adds an empty submenu and returns it */
723 - (EmacsMenu *)addSubmenuWithTitle: (char *)title forFrame: (struct frame *)f
725   NSString *titleStr = [NSString stringWithUTF8String: title];
726   NSMenuItem *item = [self addItemWithTitle: titleStr
727                                      action: nil /*@selector (menuDown:) */
728                               keyEquivalent: @""];
729   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
730   [self setSubmenu: submenu forItem: item];
731   [submenu release];
732   return submenu;
735 /* run a menu in popup mode */
736 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
737                  keymaps: (int)keymaps
739   EmacsView *view = FRAME_NS_VIEW (f);
740 /*   p = [view convertPoint:p fromView: nil]; */
741   p.y = NSHeight ([view frame]) - p.y;
742   NSEvent *e = [[view window] currentEvent];
743   NSEvent *event = [NSEvent mouseEventWithType: NSRightMouseDown
744                                       location: p
745                                  modifierFlags: 0
746                                      timestamp: [e timestamp]
747                                   windowNumber: [[view window] windowNumber]
748                                        context: [e context]
749                                    eventNumber: 0/*[e eventNumber] */
750                                     clickCount: 1
751                                       pressure: 0];
752   long retVal;
754   context_menu_value = -1;
755   [NSMenu popUpContextMenu: self withEvent: event forView: view];
756   retVal = context_menu_value;
757   context_menu_value = 0;
758   return retVal > 0
759       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
760       : Qnil;
763 @end  /* EmacsMenu */
767 /* ==========================================================================
769     Context Menu: implementing functions
771    ========================================================================== */
773 static Lisp_Object
774 cleanup_popup_menu (Lisp_Object arg)
776   discard_menu_items ();
777   return Qnil;
781 static Lisp_Object
782 ns_popup_menu (Lisp_Object position, Lisp_Object menu)
784   EmacsMenu *pmenu;
785   struct frame *f = NULL;
786   NSPoint p;
787   Lisp_Object window, x, y, tem, keymap, title;
788   struct gcpro gcpro1;
789   int specpdl_count = SPECPDL_INDEX (), specpdl_count2;
790   char *error_name = NULL;
791   int keymaps = 0;
792   widget_value *wv, *first_wv = 0;
794   NSTRACE (ns_popup_menu);
796   if (!NILP (position))
797     {
798       check_ns ();
799   
800       if (EQ (position, Qt)
801           || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
802                                    || EQ (XCAR (position), Qtool_bar))))
803         {
804           /* Use the mouse's current position.  */
805           struct frame *new_f = SELECTED_FRAME ();
807           if (FRAME_TERMINAL (new_f)->mouse_position_hook)
808             (*FRAME_TERMINAL (new_f)->mouse_position_hook)
809               (&new_f, 0, 0, 0, &x, &y, 0);
810           if (new_f != 0)
811             XSETFRAME (window, new_f);
812           else
813             {
814               window = selected_window;
815               x = make_number (0);
816               y = make_number (0);
817             }
818         }
819       else
820         {
821           CHECK_CONS (position);
822           tem = Fcar (position);
823           if (XTYPE (tem) == Lisp_Cons)
824             {
825               window = Fcar (Fcdr (position));
826               x = Fcar (tem);
827               y = Fcar (Fcdr (tem));
828             }
829           else
830             {
831               tem = Fcar (Fcdr (position));
832               window = Fcar (tem);
833               tem = Fcar (Fcdr (Fcdr (tem)));
834               x = Fcar (tem);
835               y = Fcdr (tem);
836             }
837         }
838   
839       CHECK_NUMBER (x);
840       CHECK_NUMBER (y);
842       if (FRAMEP (window))
843         {
844           f = XFRAME (window);
845       
846           p.x = 0;
847           p.y = 0;
848         }
849       else
850         {
851           struct window *win = XWINDOW (window);
852           CHECK_LIVE_WINDOW (window);
853           f = XFRAME (WINDOW_FRAME (win));
854           p.x = FRAME_COLUMN_WIDTH (f) * WINDOW_LEFT_EDGE_COL (win);
855           p.y = FRAME_LINE_HEIGHT (f) * WINDOW_TOP_EDGE_LINE (win);
856         }
858       p.x += XINT (x); p.y += XINT (y);
860       XSETFRAME (Vmenu_updating_frame, f);
861     }
862   else
863     {      /* no position given */
864       /* FIXME: if called during dump, we need to stop precomputation of
865          key equivalents (see below) because the keydefs in ns-win.el have
866          not been loaded yet. */
867       if (noninteractive)
868         return Qnil;
869       Vmenu_updating_frame = Qnil;
870     }
872   /* now parse the lisp menus */
873   record_unwind_protect (unuse_menu_items, Qnil);
874   title = Qnil;
875   GCPRO1 (title);
877   /* Decode the menu items from what was specified.  */
879   keymap = get_keymap (menu, 0, 0);
880   if (CONSP (keymap))
881     {
882       /* We were given a keymap.  Extract menu info from the keymap.  */
883       Lisp_Object prompt;
885       /* Extract the detailed info to make one pane.  */
886       keymap_panes (&menu, 1, NILP (position));
888       /* Search for a string appearing directly as an element of the keymap.
889          That string is the title of the menu.  */
890       prompt = Fkeymap_prompt (keymap);
891       title = NILP (prompt) ? build_string ("Select") : prompt;
893       /* Make that be the pane title of the first pane.  */
894       if (!NILP (prompt) && menu_items_n_panes >= 0)
895         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt;
897       keymaps = 1;
898     }
899   else if (CONSP (menu) && KEYMAPP (XCAR (menu)))
900     {
901       /* We were given a list of keymaps.  */
902       int nmaps = XFASTINT (Flength (menu));
903       Lisp_Object *maps
904         = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object));
905       int i;
907       title = Qnil;
909       /* The first keymap that has a prompt string
910          supplies the menu title.  */
911       for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem))
912         {
913           Lisp_Object prompt;
915           maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0);
917           prompt = Fkeymap_prompt (keymap);
918           if (NILP (title) && !NILP (prompt))
919             title = prompt;
920         }
922       /* Extract the detailed info to make one pane.  */
923       keymap_panes (maps, nmaps, NILP (position));
925       /* Make the title be the pane title of the first pane.  */
926       if (!NILP (title) && menu_items_n_panes >= 0)
927         XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title;
929       keymaps = 1;
930     }
931   else
932     {
933       /* We were given an old-fashioned menu.  */
934       title = Fcar (menu);
935       CHECK_STRING (title);
937       list_of_panes (Fcdr (menu));
939       keymaps = 0;
940     }
942   unbind_to (specpdl_count, Qnil);
944   /* If no position given, that was a signal to just precompute and cache
945      key equivalents, which was a side-effect of what we just did. */
946   if (NILP (position))
947     {
948       discard_menu_items ();
949       UNGCPRO;
950       return Qnil;
951     }
953   record_unwind_protect (cleanup_popup_menu, Qnil);
954   BLOCK_INPUT;
956   /* now parse stage 2 as in ns_update_menubar */
957   wv = xmalloc_widget_value ();
958   wv->name = "contextmenu";
959   wv->value = 0;
960   wv->enabled = 1;
961   wv->button_type = BUTTON_TYPE_NONE;
962   wv->help = Qnil;
963   first_wv = wv;
965   specpdl_count2 = SPECPDL_INDEX ();
967 #if 0
968   /* FIXME: a couple of one-line differences prevent reuse */
969   wv = digest_single_submenu (0, menu_items_used, Qnil);
970 #else
971   {
972   widget_value *save_wv = 0, *prev_wv = 0;
973   widget_value **submenu_stack
974     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
975 /*   Lisp_Object *subprefix_stack
976        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
977   int submenu_depth = 0;
978   int first_pane = 1;
979   int i;
981   /* Loop over all panes and items, filling in the tree.  */
982   i = 0;
983   while (i < menu_items_used)
984     {
985       if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
986         {
987           submenu_stack[submenu_depth++] = save_wv;
988           save_wv = prev_wv;
989           prev_wv = 0;
990           first_pane = 1;
991           i++;
992         }
993       else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
994         {
995           prev_wv = save_wv;
996           save_wv = submenu_stack[--submenu_depth];
997           first_pane = 0;
998           i++;
999         }
1000       else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
1001                && submenu_depth != 0)
1002         i += MENU_ITEMS_PANE_LENGTH;
1003       /* Ignore a nil in the item list.
1004          It's meaningful only for dialog boxes.  */
1005       else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
1006         i += 1;
1007       else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
1008         {
1009           /* Create a new pane.  */
1010           Lisp_Object pane_name, prefix;
1011           char *pane_string;
1013           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
1014           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
1016 #ifndef HAVE_MULTILINGUAL_MENU
1017           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
1018             {
1019               pane_name = ENCODE_MENU_STRING (pane_name);
1020               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
1021             }
1022 #endif
1023           pane_string = (NILP (pane_name)
1024                          ? "" : (char *) SDATA (pane_name));
1025           /* If there is just one top-level pane, put all its items directly
1026              under the top-level menu.  */
1027           if (menu_items_n_panes == 1)
1028             pane_string = "";
1030           /* If the pane has a meaningful name,
1031              make the pane a top-level menu item
1032              with its items as a submenu beneath it.  */
1033           if (!keymaps && strcmp (pane_string, ""))
1034             {
1035               wv = xmalloc_widget_value ();
1036               if (save_wv)
1037                 save_wv->next = wv;
1038               else
1039                 first_wv->contents = wv;
1040               wv->name = pane_string;
1041               if (keymaps && !NILP (prefix))
1042                 wv->name++;
1043               wv->value = 0;
1044               wv->enabled = 1;
1045               wv->button_type = BUTTON_TYPE_NONE;
1046               wv->help = Qnil;
1047               save_wv = wv;
1048               prev_wv = 0;
1049             }
1050           else if (first_pane)
1051             {
1052               save_wv = wv;
1053               prev_wv = 0;
1054             }
1055           first_pane = 0;
1056           i += MENU_ITEMS_PANE_LENGTH;
1057         }
1058       else
1059         {
1060           /* Create a new item within current pane.  */
1061           Lisp_Object item_name, enable, descrip, def, type, selected, help;
1062           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
1063           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
1064           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
1065           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
1066           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
1067           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
1068           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
1070 #ifndef HAVE_MULTILINGUAL_MENU
1071           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
1072             {
1073               item_name = ENCODE_MENU_STRING (item_name);
1074               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
1075             }
1077           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
1078             {
1079               descrip = ENCODE_MENU_STRING (descrip);
1080               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
1081             }
1082 #endif /* not HAVE_MULTILINGUAL_MENU */
1084           wv = xmalloc_widget_value ();
1085           if (prev_wv)
1086             prev_wv->next = wv;
1087           else
1088             save_wv->contents = wv;
1089           wv->name = (char *) SDATA (item_name);
1090           if (!NILP (descrip))
1091             wv->key = (char *) SDATA (descrip);
1092           wv->value = 0;
1093           /* If this item has a null value,
1094              make the call_data null so that it won't display a box
1095              when the mouse is on it.  */
1096           wv->call_data
1097               = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
1098           wv->enabled = !NILP (enable);
1100           if (NILP (type))
1101             wv->button_type = BUTTON_TYPE_NONE;
1102           else if (EQ (type, QCtoggle))
1103             wv->button_type = BUTTON_TYPE_TOGGLE;
1104           else if (EQ (type, QCradio))
1105             wv->button_type = BUTTON_TYPE_RADIO;
1106           else
1107             abort ();
1109           wv->selected = !NILP (selected);
1111           if (! STRINGP (help))
1112             help = Qnil;
1114           wv->help = help;
1116           prev_wv = wv;
1118           i += MENU_ITEMS_ITEM_LENGTH;
1119         }
1120     }
1121   }
1122 #endif
1124   if (!NILP (title))
1125     {
1126       widget_value *wv_title = xmalloc_widget_value ();
1127       widget_value *wv_sep = xmalloc_widget_value ();
1129       /* Maybe replace this separator with a bitmap or owner-draw item
1130          so that it looks better.  Having two separators looks odd.  */
1131       wv_sep->name = "--";
1132       wv_sep->next = first_wv->contents;
1133       wv_sep->help = Qnil;
1135 #ifndef HAVE_MULTILINGUAL_MENU
1136       if (STRING_MULTIBYTE (title))
1137         title = ENCODE_MENU_STRING (title);
1138 #endif
1140       wv_title->name = (char *) SDATA (title);
1141       wv_title->enabled = NO;
1142       wv_title->button_type = BUTTON_TYPE_NONE;
1143       wv_title->help = Qnil;
1144       wv_title->next = wv_sep;
1145       first_wv->contents = wv_title;
1146     }
1148   pmenu = [[EmacsMenu alloc] initWithTitle:
1149                                [NSString stringWithUTF8String: SDATA (title)]];
1150   [pmenu fillWithWidgetValue: first_wv->contents];
1151   free_menubar_widget_value_tree (first_wv);
1152   unbind_to (specpdl_count2, Qnil);
1154   popup_activated_flag = 1;
1155   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1156   popup_activated_flag = 0;
1157   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1159   UNBLOCK_INPUT;
1160   discard_menu_items ();
1161   unbind_to (specpdl_count, Qnil);
1162   UNGCPRO;
1164   if (error_name) error (error_name);
1165   return tem;
1171 /* ==========================================================================
1173     Toolbar: externally-called functions
1175    ========================================================================== */
1177 void
1178 free_frame_tool_bar (FRAME_PTR f)
1179 /* --------------------------------------------------------------------------
1180     Under NS we just hide the toolbar until it might be needed again.
1181    -------------------------------------------------------------------------- */
1183   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1186 void
1187 update_frame_tool_bar (FRAME_PTR f)
1188 /* --------------------------------------------------------------------------
1189     Update toolbar contents
1190    -------------------------------------------------------------------------- */
1192   int i;
1193   EmacsToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar];
1195   [toolbar clearActive];
1197   /* update EmacsToolbar as in GtkUtils, build items list */
1198   for (i = 0; i < f->n_tool_bar_items; ++i)
1199     {
1200 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1201                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1203       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1204       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1205       int idx;
1206       int img_id;
1207       struct image *img;
1208       Lisp_Object image;
1209       Lisp_Object helpObj;
1210       char *helpText;
1212       /* If image is a vector, choose the image according to the
1213          button state.  */
1214       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1215       if (VECTORP (image))
1216         {
1217           /* NS toolbar auto-computes disabled and selected images */
1218           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1219           xassert (ASIZE (image) >= idx);
1220           image = AREF (image, idx);
1221         }
1222       else
1223         {
1224           idx = -1;
1225         }
1226       /* Ignore invalid image specifications.  */
1227       if (!valid_image_p (image))
1228         {
1229           NSLog (@"Invalid image for toolbar item");
1230           continue;
1231         }
1233       img_id = lookup_image (f, image);
1234       img = IMAGE_FROM_ID (f, img_id);
1235       prepare_image_for_display (f, img);
1237       if (img->load_failed_p || img->pixmap == nil)
1238         {
1239           NSLog (@"Could not prepare toolbar image for display.");
1240           continue;
1241         }
1243       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1244       if (NILP (helpObj))
1245         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1246       helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1248       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1249                                enabled: enabled_p];
1250 #undef TOOLPROP
1251     }
1253   if (![toolbar isVisible])
1254       [toolbar setVisible: YES];
1256   if ([toolbar changed])
1257     {
1258       /* inform app that toolbar has changed */
1259       NSDictionary *dict = [toolbar configurationDictionary];
1260       NSMutableDictionary *newDict = [dict mutableCopy];
1261       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1262       NSObject *key;
1263       while ((key = [keys nextObject]) != nil)
1264         {
1265           NSObject *val = [dict objectForKey: key];
1266           if ([val isKindOfClass: [NSArray class]])
1267             {
1268               [newDict setObject:
1269                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1270                           forKey: key];
1271               break;
1272             }
1273         }
1274       [toolbar setConfigurationFromDictionary: newDict];
1275       [newDict release];
1276     }
1281 /* ==========================================================================
1283     Toolbar: class implementation
1285    ========================================================================== */
1287 @implementation EmacsToolbar
1289 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1291   self = [super initWithIdentifier: identifier];
1292   emacsView = view;
1293   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1294   [self setSizeMode: NSToolbarSizeModeSmall];
1295   [self setDelegate: self];
1296   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1297   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1298   prevEnablement = enablement = 0L;
1299   return self;
1302 - (void)dealloc
1304   [prevIdentifiers release];
1305   [activeIdentifiers release];
1306   [identifierToItem release];
1307   [super dealloc];
1310 - (void) clearActive
1312   [prevIdentifiers release];
1313   prevIdentifiers = [activeIdentifiers copy];
1314   [activeIdentifiers removeAllObjects];
1315   prevEnablement = enablement;
1316   enablement = 0L;
1319 - (BOOL) changed
1321   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1322     enablement == prevEnablement ? NO : YES;
1325 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1326                         helpText: (char *)help enabled: (BOOL)enabled
1328   /* 1) come up w/identifier */
1329   NSString *identifier
1330       = [NSString stringWithFormat: @"%u", [img hash]];
1332   /* 2) create / reuse item */
1333   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1334   if (item == nil)
1335     {
1336       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1337                autorelease];
1338       [item setImage: img];
1339       [item setToolTip: [NSString stringWithCString: help]];
1340       [item setTarget: emacsView];
1341       [item setAction: @selector (toolbarClicked:)];
1342     }
1344   [item setTag: idx];
1345   [item setEnabled: enabled];
1347   /* 3) update state */
1348   [identifierToItem setObject: item forKey: identifier];
1349   [activeIdentifiers addObject: identifier];
1350   enablement = (enablement << 1) | (enabled == YES);
1353 /* This overrides super's implementation, which automatically sets
1354    all items to enabled state (for some reason). */
1355 - (void)validateVisibleItems { }
1358 /* delegate methods */
1360 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1361       itemForItemIdentifier: (NSString *)itemIdentifier
1362   willBeInsertedIntoToolbar: (BOOL)flag
1364   /* look up NSToolbarItem by identifier and return... */
1365   return [identifierToItem objectForKey: itemIdentifier];
1368 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1370   /* return entire set.. */
1371   return activeIdentifiers;
1374 /* for configuration palette (not yet supported) */
1375 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1377   /* return entire set... */
1378   return [identifierToItem allKeys];
1381 /* optional and unneeded */
1382 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1383 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1384 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1386 @end  /* EmacsToolbar */
1390 /* ==========================================================================
1392     Tooltip: class implementation
1394    ========================================================================== */
1396 /* Needed because NeXTstep does not provide enough control over tooltip
1397    display. */
1398 @implementation EmacsTooltip
1400 - init
1402   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1403                                             blue: 0.792 alpha: 0.95];
1404   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1405   NSFont *sfont = [font screenFont];
1406   int height = [sfont ascender] - [sfont descender];
1407 /*[font boundingRectForFont].size.height; */
1408   NSRect r = NSMakeRect (0, 0, 100, height+6);
1410   textField = [[NSTextField alloc] initWithFrame: r];
1411   [textField setFont: font];
1412   [textField setBackgroundColor: col];
1414   [textField setEditable: NO];
1415   [textField setSelectable: NO];
1416   [textField setBordered: YES];
1417   [textField setBezeled: YES];
1418   [textField setDrawsBackground: YES];
1420   win = [[NSWindow alloc]
1421             initWithContentRect: [textField frame]
1422                       styleMask: 0
1423                         backing: NSBackingStoreBuffered
1424                           defer: YES];
1425   [win setReleasedWhenClosed: NO];
1426   [win setDelegate: self];
1427   [[win contentView] addSubview: textField];
1428 /*  [win setBackgroundColor: col]; */
1429   [win setOpaque: NO];
1431   return self;
1434 - (void) dealloc
1436   [win close];
1437   [win release];
1438   [textField release];
1439   [super dealloc];
1442 - (void) setText: (char *)text
1444   NSString *str = [NSString stringWithUTF8String: text];
1445   NSRect r = [textField frame];
1446   NSSize textSize = [str sizeWithAttributes: 
1447      [NSDictionary dictionaryWithObject: [[textField font] screenFont]
1448                                  forKey: NSFontAttributeName]];
1449   NSSize padSize = [[[textField font] screenFont] 
1450                      boundingRectForFont].size;
1452   r.size.width = textSize.width + padSize.width/2;
1453   r.size.height = textSize.height + padSize.height/2;
1454   [textField setFrame: r];
1455   [textField setStringValue: str];
1458 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1460   NSRect wr = [win frame];
1462   wr.origin = NSMakePoint (x, y);
1463   wr.size = [textField frame].size;
1465   [win setFrame: wr display: YES];
1466   [win orderFront: self];
1467   [win display];
1468   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1469                                          selector: @selector (hide)
1470                                          userInfo: nil repeats: NO];
1471   [timer retain];
1474 - (void) hide
1476   [win close];
1477   if (timer != nil)
1478     {
1479       if ([timer isValid])
1480         [timer invalidate];
1481       [timer release];
1482       timer = nil;
1483     }
1486 - (BOOL) isActive
1488   return timer != nil;
1491 - (NSRect) frame
1493   return [textField frame];
1496 @end  /* EmacsTooltip */
1500 /* ==========================================================================
1502     Popup Dialog: implementing functions
1504    ========================================================================== */
1507 static Lisp_Object
1508 pop_down_menu (Lisp_Object arg)
1510   struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1511   if (popup_activated_flag)
1512     {
1513       popup_activated_flag = 0;
1514       BLOCK_INPUT;
1515       [NSApp endModalSession: popupSession];
1516       [((EmacsDialogPanel *) (p->pointer)) close];
1517       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1518       UNBLOCK_INPUT;
1519     }
1520   return Qnil;
1524 Lisp_Object
1525 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1527   id dialog;
1528   Lisp_Object window, tem;
1529   struct frame *f;
1530   NSPoint p;
1531   BOOL isQ;
1533   NSTRACE (x-popup-dialog);
1534   
1535   check_ns ();
1537   isQ = NILP (header);
1539   if (EQ (position, Qt)
1540       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1541                                || EQ (XCAR (position), Qtool_bar))))
1542     {
1543       window = selected_window;
1544     }
1545   else if (CONSP (position))
1546     {
1547       Lisp_Object tem;
1548       tem = Fcar (position);
1549       if (XTYPE (tem) == Lisp_Cons)
1550         window = Fcar (Fcdr (position));
1551       else
1552         {
1553           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1554           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1555         }
1556     }
1557   else if (WINDOWP (position) || FRAMEP (position))
1558     {
1559       window = position;
1560     }
1561   else
1562     window = Qnil;
1564   if (FRAMEP (window))
1565     f = XFRAME (window);
1566   else if (WINDOWP (window))
1567     {
1568       CHECK_LIVE_WINDOW (window);
1569       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1570     }
1571   else
1572     CHECK_WINDOW (window);
1574   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1575   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1577   BLOCK_INPUT;
1578   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1579                                            isQuestion: isQ];
1580   {
1581     int specpdl_count = SPECPDL_INDEX ();
1582     record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1583     popup_activated_flag = 1;
1584     tem = [dialog runDialogAt: p];
1585     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1586   }
1587   UNBLOCK_INPUT;
1589   return tem;
1593 /* ==========================================================================
1595     Popup Dialog: class implementation
1597    ========================================================================== */
1599 @interface FlippedView : NSView
1602 @end
1604 @implementation FlippedView
1605 - (BOOL)isFlipped
1607   return YES;
1609 @end
1611 @implementation EmacsDialogPanel
1613 #define SPACER          8.0
1614 #define ICONSIZE        64.0
1615 #define TEXTHEIGHT      20.0
1616 #define MINCELLWIDTH    90.0
1618 - initWithContentRect: (NSRect)contentRect styleMask: (unsigned int)aStyle
1619               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1621   NSSize spacing = {SPACER, SPACER};
1622   NSRect area;
1623   char this_cmd_name[80];
1624   id cell;
1625   static NSImageView *imgView;
1626   static FlippedView *contentView;
1628   if (imgView == nil)
1629     {
1630       NSImage *img;
1631       area.origin.x   = 3*SPACER;
1632       area.origin.y   = 2*SPACER;
1633       area.size.width = ICONSIZE;
1634       area.size.height= ICONSIZE;
1635       img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1636       [img setScalesWhenResized: YES];
1637       [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1638       imgView = [[NSImageView alloc] initWithFrame: area];
1639       [imgView setImage: img];
1640       [imgView setEditable: NO];
1641       [img release];
1642     }
1644   aStyle = NSTitledWindowMask;
1645   flag = YES;
1646   rows = 0;
1647   cols = 1;
1648   [super initWithContentRect: contentRect styleMask: aStyle
1649                      backing: backingType defer: flag];
1650   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1651   [self setContentView: contentView];
1653   [[self contentView] setAutoresizesSubviews: YES];
1655   [[self contentView] addSubview: imgView];
1656   [self setTitle: @""];
1658   area.origin.x   += ICONSIZE+2*SPACER;
1659 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1660   area.size.width = 400;
1661   area.size.height= TEXTHEIGHT;
1662   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1663   [[self contentView] addSubview: command];
1664   [command setStringValue: ns_app_name];
1665   [command setDrawsBackground: NO];
1666   [command setBezeled: NO];
1667   [command setSelectable: NO];
1668   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1670 /*  area.origin.x   = ICONSIZE+2*SPACER;
1671   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1672   area.size.width = 400;
1673   area.size.height= 2;
1674   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1675   [[self contentView] addSubview: tem];
1676   [tem setTitlePosition: NSNoTitle];
1677   [tem setAutoresizingMask: NSViewWidthSizable];*/
1679 /*  area.origin.x = ICONSIZE+2*SPACER; */
1680   area.origin.y += TEXTHEIGHT+SPACER;
1681   area.size.width = 400;
1682   area.size.height= TEXTHEIGHT;
1683   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1684   [[self contentView] addSubview: title];
1685   [title setDrawsBackground: NO];
1686   [title setBezeled: NO];
1687   [title setSelectable: NO];
1688   [title setFont: [NSFont systemFontOfSize: 11.0]];
1690   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1691   [cell setBordered: NO];
1692   [cell setEnabled: NO];
1693   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1694   [cell setBezelStyle: NSRoundedBezelStyle];
1696   matrix = [[NSMatrix alloc] initWithFrame: contentRect 
1697                                       mode: NSHighlightModeMatrix 
1698                                  prototype: cell 
1699                               numberOfRows: 0 
1700                            numberOfColumns: 1];
1701   [[self contentView] addSubview: matrix];
1702   [matrix release];
1703   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1704                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1705   [matrix setIntercellSpacing: spacing];
1707   [self setOneShot: YES];
1708   [self setReleasedWhenClosed: YES];
1709   [self setHidesOnDeactivate: YES];
1710   return self;
1714 - (BOOL)windowShouldClose: (id)sender
1716   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1717   return NO;
1721 void process_dialog (id window, Lisp_Object list)
1723   Lisp_Object item;
1724   int row = 0;
1726   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1727     {
1728       item = XCAR (list);
1729       if (XTYPE (item) == Lisp_String)
1730         {
1731           [window addString: SDATA (item) row: row++];
1732         }
1733       else if (XTYPE (item) == Lisp_Cons)
1734         {
1735           [window addButton: SDATA (XCAR (item))
1736                       value: XCDR (item) row: row++];
1737         }
1738       else if (NILP (item))
1739         {
1740           [window addSplit];
1741           row = 0;
1742         }
1743     }
1747 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1749   id cell;
1750        
1751   if (row >= rows)
1752     {
1753       [matrix addRow];
1754       rows++;
1755     }
1756   cell = [matrix cellAtRow: row column: cols-1];
1757   [cell setTarget: self];
1758   [cell setAction: @selector (clicked: )];
1759   [cell setTitle: [NSString stringWithUTF8String: str]];
1760   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1761   [cell setBordered: YES];
1762   [cell setEnabled: YES];
1764   return self;
1768 - addString: (char *)str row: (int)row
1770   id cell;
1771        
1772   if (row >= rows)
1773     {
1774       [matrix addRow];
1775       rows++;
1776     }
1777   cell = [matrix cellAtRow: row column: cols-1];
1778   [cell setTitle: [NSString stringWithUTF8String: str]];
1779   [cell setBordered: YES];
1780   [cell setEnabled: NO];
1782   return self;
1786 - addSplit
1788   [matrix addColumn];
1789   cols++;
1790   return self;
1794 - clicked: sender
1796   NSArray *sellist = nil;
1797   EMACS_INT seltag;
1799   sellist = [sender selectedCells];
1800   if ([sellist count]<1) 
1801     return self;
1803   seltag = [[sellist objectAtIndex: 0] tag];
1804   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1805     [NSApp stopModalWithCode: seltag];
1806   return self;
1810 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1812   Lisp_Object head;
1813   [super init];
1815   if (XTYPE (contents) == Lisp_Cons)
1816     {
1817       head = Fcar (contents);
1818       process_dialog (self, Fcdr (contents));
1819     }
1820   else
1821     head = contents;
1823   if (XTYPE (head) == Lisp_String)
1824       [title setStringValue:
1825                  [NSString stringWithUTF8String: SDATA (head)]];
1826   else if (isQ == YES)
1827       [title setStringValue: @"Question"];
1828   else
1829       [title setStringValue: @"Information"];
1831   {
1832     int i;
1833     NSRect r, s, t;
1835     if (cols == 1 && rows > 1)  /* Never told where to split */
1836       {
1837         [matrix addColumn];
1838         for (i = 0; i<rows/2; i++)
1839           {
1840             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1841                       atRow: i column: 1];
1842             [matrix removeRow: (rows+1)/2];
1843           }
1844       }
1846     [matrix sizeToFit];
1847     {
1848       NSSize csize = [matrix cellSize];
1849       if (csize.width < MINCELLWIDTH)
1850         {
1851           csize.width = MINCELLWIDTH;
1852           [matrix setCellSize: csize];
1853           [matrix sizeToCells];
1854         }
1855     }
1857     [title sizeToFit];
1858     [command sizeToFit];
1860     t = [matrix frame];
1861     r = [title frame];
1862     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1863       {
1864         t.origin.x   = r.origin.x;
1865         t.size.width = r.size.width;
1866       }
1867     r = [command frame];
1868     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1869       {
1870         t.origin.x   = r.origin.x;
1871         t.size.width = r.size.width;
1872       }
1874     r = [self frame];
1875     s = [(NSView *)[self contentView] frame];
1876     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1877     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1878     [self setFrame: r display: NO];
1879   }
1881   return self;
1885 - (void)dealloc
1887   { [super dealloc]; return; };
1891 - (Lisp_Object)runDialogAt: (NSPoint)p
1893   int ret;
1894   extern EMACS_TIME timer_check (int do_it_now); /* TODO: add to a header */
1896   /* initiate a session that will be ended by pop_down_menu */
1897   popupSession = [NSApp beginModalSessionForWindow: self];
1898   while (popup_activated_flag
1899          && (ret = [NSApp runModalSession: popupSession])
1900               == NSRunContinuesResponse)
1901     {
1902       /* Run this for timers.el, indep of atimers; might not return.
1903          TODO: use return value to avoid calling every iteration. */
1904       timer_check (1);
1905       [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1906     }
1908   {                             /* FIXME: BIG UGLY HACK!!! */
1909       Lisp_Object tmp;
1910       *(EMACS_INT*)(&tmp) = ret;
1911       return tmp;
1912   }
1915 @end
1918 /* ==========================================================================
1920     Lisp definitions
1922    ========================================================================== */
1924 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1925        doc: /* Cause the NS menu to be re-calculated.  */)
1926      ()
1928   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1929   return Qnil;
1933 DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
1934        doc: /* Pop up a deck-of-cards menu and return user's selection.
1935 POSITION is a position specification.  This is either a mouse button event
1936 or a list ((XOFFSET YOFFSET) WINDOW)
1937 where XOFFSET and YOFFSET are positions in pixels from the top left
1938 corner of WINDOW.  (WINDOW may be a window or a frame object.)
1939 This controls the position of the top left of the menu as a whole.
1940 If POSITION is t, it means to use the current mouse position.
1942 MENU is a specifier for a menu.  For the simplest case, MENU is a keymap.
1943 The menu items come from key bindings that have a menu string as well as
1944 a definition; actually, the \"definition\" in such a key binding looks like
1945 \(STRING . REAL-DEFINITION).  To give the menu a title, put a string into
1946 the keymap as a top-level element.
1948 If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.
1949 Otherwise, REAL-DEFINITION should be a valid key binding definition.
1951 You can also use a list of keymaps as MENU.
1952   Then each keymap makes a separate pane.
1954 When MENU is a keymap or a list of keymaps, the return value is the
1955 list of events corresponding to the user's choice. Note that
1956 `x-popup-menu' does not actually execute the command bound to that
1957 sequence of events.
1959 Alternatively, you can specify a menu of multiple panes
1960   with a list of the form (TITLE PANE1 PANE2...),
1961 where each pane is a list of form (TITLE ITEM1 ITEM2...).
1962 Each ITEM is normally a cons cell (STRING . VALUE);
1963 but a string can appear as an item--that makes a nonselectable line
1964 in the menu.
1965 With this form of menu, the return value is VALUE from the chosen item.  */)
1966      (position, menu)
1967      Lisp_Object position, menu;
1969   return ns_popup_menu (position, menu);
1973 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1974        doc: /* Pop up a dialog box and return user's selection.
1975 POSITION specifies which frame to use.
1976 This is normally a mouse button event or a window or frame.
1977 If POSITION is t, it means to use the frame the mouse is on.
1978 The dialog box appears in the middle of the specified frame.
1980 CONTENTS specifies the alternatives to display in the dialog box.
1981 It is a list of the form (DIALOG ITEM1 ITEM2...).
1982 Each ITEM is a cons cell (STRING . VALUE).
1983 The return value is VALUE from the chosen item.
1985 An ITEM may also be just a string--that makes a nonselectable item.
1986 An ITEM may also be nil--that means to put all preceding items
1987 on the left of the dialog box and all following items on the right.
1988 \(By default, approximately half appear on each side.)
1990 If HEADER is non-nil, the frame title for the box is "Information",
1991 otherwise it is "Question".
1993 If the user gets rid of the dialog box without making a valid choice,
1994 for instance using the window manager, then this produces a quit and
1995 `x-popup-dialog' does not return.  */)
1996      (position, contents, header)
1997      Lisp_Object position, contents, header;
1999   return ns_popup_dialog (position, contents, header);
2002 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
2003        doc: /* Return t if a menu or popup dialog is active.  */)
2004      ()
2006   return popup_activated () ? Qt : Qnil;
2009 /* ==========================================================================
2011     Lisp interface declaration
2013    ========================================================================== */
2015 void
2016 syms_of_nsmenu ()
2018   defsubr (&Sx_popup_menu);
2019   defsubr (&Sx_popup_dialog);
2020   defsubr (&Sns_reset_menu);
2021   defsubr (&Smenu_or_popup_active_p);
2022   staticpro (&menu_items);
2023   menu_items = Qnil;
2025   Qdebug_on_next_call = intern ("debug-on-next-call");
2026   staticpro (&Qdebug_on_next_call);
2029 // arch-tag: 75773656-52e5-4c44-a398-47bd87b32619