; Merge from origin/emacs-25
[emacs.git] / src / nsmenu.m
blob7d340e8ec8353b7f2760bb226f98879966a9dd56
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007-2016 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 (at
9 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 "character.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"
39 #include "menu.h"
41 #define NSMENUPROFILE 0
43 #if NSMENUPROFILE
44 #include <sys/timeb.h>
45 #include <sys/types.h>
46 #endif
49 #if 0
50 /* Include lisp -> C common menu parsing code */
51 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
52 #include "nsmenu_common.c"
53 #endif
55 extern long context_menu_value;
56 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
58 /* Nonzero means a menu is currently active.  */
59 static int popup_activated_flag;
61 /* Nonzero means we are tracking and updating menus.  */
62 static int trackingMenu;
65 /* NOTE: toolbar implementation is at end,
66   following complete menu implementation. */
69 /* ==========================================================================
71     Menu: Externally-called functions
73    ========================================================================== */
76 /* Supposed to discard menubar and free storage.  Since we share the
77    menubar among frames and update its context for the focused window,
78    there is nothing to do here. */
79 void
80 free_frame_menubar (struct frame *f)
82   return;
86 int
87 popup_activated (void)
89   return popup_activated_flag;
93 /* --------------------------------------------------------------------------
94     Update menubar.  Three cases:
95     1) ! deep_p, submenu = nil: Fresh switch onto a frame -- either set up
96        just top-level menu strings (OS X), or goto case (2) (GNUstep).
97     2) deep_p, submenu = nil: Recompute all submenus.
98     3) deep_p, submenu = non-nil: Update contents of a single submenu.
99    -------------------------------------------------------------------------- */
100 static void
101 ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu)
103   NSAutoreleasePool *pool;
104   id menu = [NSApp mainMenu];
105   static EmacsMenu *last_submenu = nil;
106   BOOL needsSet = NO;
107   bool owfi;
108   Lisp_Object items;
109   widget_value *wv, *first_wv, *prev_wv = 0;
110   int i;
112 #if NSMENUPROFILE
113   struct timeb tb;
114   long t;
115 #endif
117   NSTRACE ("ns_update_menubar");
119   if (f != SELECTED_FRAME ())
120       return;
121   XSETFRAME (Vmenu_updating_frame, f);
122 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
124   block_input ();
125   pool = [[NSAutoreleasePool alloc] init];
127   /* Menu may have been created automatically; if so, discard it. */
128   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
129     {
130       [menu release];
131       menu = nil;
132     }
134   if (menu == nil)
135     {
136       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
137       needsSet = YES;
138     }
139   else
140     {  /* close up anything on there */
141       id attMenu = [menu attachedMenu];
142       if (attMenu != nil)
143         [attMenu close];
144     }
146 #if NSMENUPROFILE
147   ftime (&tb);
148   t = -(1000*tb.time+tb.millitm);
149 #endif
151 #ifdef NS_IMPL_GNUSTEP
152   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
153 #endif
155   if (deep_p)
156     {
157       /* Fully parse one or more of the submenus. */
158       int n = 0;
159       int *submenu_start, *submenu_end;
160       bool *submenu_top_level_items;
161       int *submenu_n_panes;
162       struct buffer *prev = current_buffer;
163       Lisp_Object buffer;
164       ptrdiff_t specpdl_count = SPECPDL_INDEX ();
165       int previous_menu_items_used = f->menu_bar_items_used;
166       Lisp_Object *previous_items
167         = alloca (previous_menu_items_used * sizeof *previous_items);
169       /* lisp preliminaries */
170       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->contents;
171       specbind (Qinhibit_quit, Qt);
172       specbind (Qdebug_on_next_call, Qnil);
173       record_unwind_save_match_data ();
174       if (NILP (Voverriding_local_map_menu_flag))
175         {
176           specbind (Qoverriding_terminal_local_map, Qnil);
177           specbind (Qoverriding_local_map, Qnil);
178         }
179       set_buffer_internal_1 (XBUFFER (buffer));
181       /* TODO: for some reason this is not needed in other terms,
182            but some menu updates call Info-extract-pointer which causes
183            abort-on-error if waiting-for-input.  Needs further investigation. */
184       owfi = waiting_for_input;
185       waiting_for_input = 0;
187       /* lucid hook and possible reset */
188       safe_run_hooks (Qactivate_menubar_hook);
189       if (! NILP (Vlucid_menu_bar_dirty_flag))
190         call0 (Qrecompute_lucid_menubar);
191       safe_run_hooks (Qmenu_bar_update_hook);
192       fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
194       /* Now ready to go */
195       items = FRAME_MENU_BAR_ITEMS (f);
197       /* Save the frame's previous menu bar contents data */
198       if (previous_menu_items_used)
199         memcpy (previous_items, aref_addr (f->menu_bar_vector, 0),
200                 previous_menu_items_used * sizeof (Lisp_Object));
202       /* parse stage 1: extract from lisp */
203       save_menu_items ();
205       menu_items = f->menu_bar_vector;
206       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
207       submenu_start = alloca (ASIZE (items) * sizeof *submenu_start);
208       submenu_end = alloca (ASIZE (items) * sizeof *submenu_end);
209       submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes);
210       submenu_top_level_items = alloca (ASIZE (items)
211                                         * sizeof *submenu_top_level_items);
212       init_menu_items ();
213       for (i = 0; i < ASIZE (items); i += 4)
214         {
215           Lisp_Object key, string, maps;
217           key = AREF (items, i);
218           string = AREF (items, i + 1);
219           maps = AREF (items, i + 2);
220           if (NILP (string))
221             break;
223           /* FIXME: we'd like to only parse the needed submenu, but this
224                was causing crashes in the _common parsing code.. need to make
225                sure proper initialization done.. */
226 /*        if (submenu && strcmp ([[submenu title] UTF8String], SSDATA (string)))
227              continue; */
229           submenu_start[i] = menu_items_used;
231           menu_items_n_panes = 0;
232           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
233           submenu_n_panes[i] = menu_items_n_panes;
234           submenu_end[i] = menu_items_used;
235           n++;
236         }
238       finish_menu_items ();
239       waiting_for_input = owfi;
242       if (submenu && n == 0)
243         {
244           /* should have found a menu for this one but didn't */
245           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
246                   [[submenu title] UTF8String]);
247           discard_menu_items ();
248           unbind_to (specpdl_count, Qnil);
249           [pool release];
250           unblock_input ();
251           return;
252         }
254       /* parse stage 2: insert into lucid 'widget_value' structures
255          [comments in other terms say not to evaluate lisp code here] */
256       wv = make_widget_value ("menubar", NULL, true, Qnil);
257       wv->button_type = BUTTON_TYPE_NONE;
258       first_wv = wv;
260       for (i = 0; i < 4*n; i += 4)
261         {
262           menu_items_n_panes = submenu_n_panes[i];
263           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
264                                       submenu_top_level_items[i]);
265           if (prev_wv)
266             prev_wv->next = wv;
267           else
268             first_wv->contents = wv;
269           /* Don't set wv->name here; GC during the loop might relocate it.  */
270           wv->enabled = 1;
271           wv->button_type = BUTTON_TYPE_NONE;
272           prev_wv = wv;
273         }
275       set_buffer_internal_1 (prev);
277       /* Compare the new menu items with previous, and leave off if no change */
278       /* FIXME: following other terms here, but seems like this should be
279            done before parse stage 2 above, since its results aren't used */
280       if (previous_menu_items_used
281           && (!submenu || (submenu && submenu == last_submenu))
282           && menu_items_used == previous_menu_items_used)
283         {
284           for (i = 0; i < previous_menu_items_used; i++)
285             /* FIXME: this ALWAYS fails on Buffers menu items.. something
286                  about their strings causes them to change every time, so we
287                  double-check failures */
288             if (!EQ (previous_items[i], AREF (menu_items, i)))
289               if (!(STRINGP (previous_items[i])
290                     && STRINGP (AREF (menu_items, i))
291                     && !strcmp (SSDATA (previous_items[i]),
292                                 SSDATA (AREF (menu_items, i)))))
293                   break;
294           if (i == previous_menu_items_used)
295             {
296               /* No change.. */
298 #if NSMENUPROFILE
299               ftime (&tb);
300               t += 1000*tb.time+tb.millitm;
301               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
302 #endif
304               free_menubar_widget_value_tree (first_wv);
305               discard_menu_items ();
306               unbind_to (specpdl_count, Qnil);
307               [pool release];
308               unblock_input ();
309               return;
310             }
311         }
312       /* The menu items are different, so store them in the frame */
313       /* FIXME: this is not correct for single-submenu case */
314       fset_menu_bar_vector (f, menu_items);
315       f->menu_bar_items_used = menu_items_used;
317       /* Calls restore_menu_items, etc., as they were outside */
318       unbind_to (specpdl_count, Qnil);
320       /* Parse stage 2a: now GC cannot happen during the lifetime of the
321          widget_value, so it's safe to store data from a Lisp_String */
322       wv = first_wv->contents;
323       for (i = 0; i < ASIZE (items); i += 4)
324         {
325           Lisp_Object string;
326           string = AREF (items, i + 1);
327           if (NILP (string))
328             break;
330           wv->name = SSDATA (string);
331           update_submenu_strings (wv->contents);
332           wv = wv->next;
333         }
335       /* Now, update the NS menu; if we have a submenu, use that, otherwise
336          create a new menu for each sub and fill it. */
337       if (submenu)
338         {
339           const char *submenuTitle = [[submenu title] UTF8String];
340           for (wv = first_wv->contents; wv; wv = wv->next)
341             {
342               if (!strcmp (submenuTitle, wv->name))
343                 {
344                   [submenu fillWithWidgetValue: wv->contents];
345                   last_submenu = submenu;
346                   break;
347                 }
348             }
349         }
350       else
351         {
352           [menu fillWithWidgetValue: first_wv->contents frame: f];
353         }
355     }
356   else
357     {
358       static int n_previous_strings = 0;
359       static char previous_strings[100][10];
360       static struct frame *last_f = NULL;
361       int n;
362       Lisp_Object string;
364       wv = make_widget_value ("menubar", NULL, true, Qnil);
365       wv->button_type = BUTTON_TYPE_NONE;
366       first_wv = wv;
368       /* Make widget-value tree w/ just the top level menu bar strings */
369       items = FRAME_MENU_BAR_ITEMS (f);
370       if (NILP (items))
371         {
372           free_menubar_widget_value_tree (first_wv);
373           [pool release];
374           unblock_input ();
375           return;
376         }
379       /* check if no change.. this mechanism is a bit rough, but ready */
380       n = ASIZE (items) / 4;
381       if (f == last_f && n_previous_strings == n)
382         {
383           for (i = 0; i<n; i++)
384             {
385               string = AREF (items, 4*i+1);
387               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
388                 continue;
389               if (NILP (string))
390                 {
391                   if (previous_strings[i][0])
392                     break;
393                   else
394                     continue;
395                 }
396               else if (memcmp (previous_strings[i], SDATA (string),
397                           min (10, SBYTES (string) + 1)))
398                 break;
399             }
401           if (i == n)
402             {
403               free_menubar_widget_value_tree (first_wv);
404               [pool release];
405               unblock_input ();
406               return;
407             }
408         }
410       [menu clear];
411       for (i = 0; i < ASIZE (items); i += 4)
412         {
413           string = AREF (items, i + 1);
414           if (NILP (string))
415             break;
417           if (n < 100)
418             memcpy (previous_strings[i/4], SDATA (string),
419                     min (10, SBYTES (string) + 1));
421           wv = make_widget_value (SSDATA (string), NULL, true, Qnil);
422           wv->button_type = BUTTON_TYPE_NONE;
423           wv->call_data = (void *) (intptr_t) (-1);
425 #ifdef NS_IMPL_COCOA
426           /* we'll update the real copy under app menu when time comes */
427           if (!strcmp ("Services", wv->name))
428             {
429               /* but we need to make sure it will update on demand */
430               [svcsMenu setFrame: f];
431             }
432           else
433 #endif
434           [menu addSubmenuWithTitle: wv->name forFrame: f];
436           if (prev_wv)
437             prev_wv->next = wv;
438           else
439             first_wv->contents = wv;
440           prev_wv = wv;
441         }
443       last_f = f;
444       if (n < 100)
445         n_previous_strings = n;
446       else
447         n_previous_strings = 0;
449     }
450   free_menubar_widget_value_tree (first_wv);
453 #if NSMENUPROFILE
454   ftime (&tb);
455   t += 1000*tb.time+tb.millitm;
456   fprintf (stderr, "Menu update took %ld msec.\n", t);
457 #endif
459   /* set main menu */
460   if (needsSet)
461     [NSApp setMainMenu: menu];
463   [pool release];
464   unblock_input ();
469 /* Main emacs core entry point for menubar menus: called to indicate that the
470    frame's menus have changed, and the *step representation should be updated
471    from Lisp. */
472 void
473 set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
475   ns_update_menubar (f, deep_p, nil);
478 void
479 x_activate_menubar (struct frame *f)
481 #ifdef NS_IMPL_COCOA
482   ns_update_menubar (f, true, nil);
483   ns_check_pending_open_menu ();
484 #endif
490 /* ==========================================================================
492     Menu: class implementation
494    ========================================================================== */
497 /* Menu that can define itself from Emacs "widget_value"s and will lazily
498    update itself when user clicked.  Based on Carbon/AppKit implementation
499    by Yamamoto Mitsuharu. */
500 @implementation EmacsMenu
502 /* override designated initializer */
503 - initWithTitle: (NSString *)title
505   frame = 0;
506   if ((self = [super initWithTitle: title]))
507     [self setAutoenablesItems: NO];
508   return self;
512 /* used for top-level */
513 - initWithTitle: (NSString *)title frame: (struct frame *)f
515   [self initWithTitle: title];
516   frame = f;
517 #ifdef NS_IMPL_COCOA
518   [self setDelegate: self];
519 #endif
520   return self;
524 - (void)setFrame: (struct frame *)f
526   frame = f;
529 #ifdef NS_IMPL_COCOA
530 -(void)trackingNotification:(NSNotification *)notification
532   /* Update menu in menuNeedsUpdate only while tracking menus.  */
533   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
534                   ? 1 : 0);
535   if (! trackingMenu) ns_check_menu_open (nil);
538 - (void)menuWillOpen:(NSMenu *)menu
540   ++trackingMenu;
542 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
543   // On 10.6 we get repeated calls, only the one for NSSystemDefined is "real".
544   if ([[NSApp currentEvent] type] != NSSystemDefined) return;
545 #endif
547   /* When dragging from one menu to another, we get willOpen followed by didClose,
548      i.e. trackingMenu == 3 in willOpen and then 2 after didClose.
549      We have updated all menus, so avoid doing it when trackingMenu == 3.  */
550   if (trackingMenu == 2)
551     ns_check_menu_open (menu);
554 - (void)menuDidClose:(NSMenu *)menu
556   --trackingMenu;
559 #endif /* NS_IMPL_COCOA */
561 /* delegate method called when a submenu is being opened: run a 'deep' call
562    to set_frame_menubar */
563 - (void)menuNeedsUpdate: (NSMenu *)menu
565   if (!FRAME_LIVE_P (frame))
566     return;
568   /* Cocoa/Carbon will request update on every keystroke
569      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
570      since key equivalents are handled through emacs.
571      On Leopard, even keystroke events generate SystemDefined event.
572      Third-party applications that enhance mouse / trackpad
573      interaction, or also VNC/Remote Desktop will send events
574      of type AppDefined rather than SysDefined.
575      Menus will fail to show up if they haven't been initialized.
576      AppDefined events may lack timing data.
578      Thus, we rely on the didBeginTrackingNotification notification
579      as above to indicate the need for updates.
580      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
581      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
582   */
583   if (trackingMenu == 0)
584     return;
585 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
586 #ifdef NS_IMPL_GNUSTEP
587   /* Don't know how to do this for anything other than OSX >= 10.5
588      This is wrong, as it might run Lisp code in the event loop.  */
589   ns_update_menubar (frame, true, self);
590 #endif
594 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
596   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
597       && FRAME_NS_VIEW (SELECTED_FRAME ()))
598     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
599   return YES;
603 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
604    into an accelerator string.  We are only able to display a single character
605    for an accelerator, together with an optional modifier combination.  (Under
606    Carbon more control was possible, but in Cocoa multi-char strings passed to
607    NSMenuItem get ignored.  For now we try to display a super-single letter
608    combo, and return the others as strings to be appended to the item title.
609    (This is signaled by setting keyEquivModMask to 0 for now.) */
610 -(NSString *)parseKeyEquiv: (const char *)key
612   const char *tpos = key;
613   keyEquivModMask = NSCommandKeyMask;
615   if (!key || !strlen (key))
616     return @"";
618   while (*tpos == ' ' || *tpos == '(')
619     tpos++;
620   if ((*tpos == 's') && (*(tpos+1) == '-'))
621     {
622       return [NSString stringWithFormat: @"%c", tpos[2]];
623     }
624   keyEquivModMask = 0; /* signal */
625   return [NSString stringWithUTF8String: tpos];
629 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
631   NSMenuItem *item;
632   widget_value *wv = (widget_value *)wvptr;
634   if (menu_separator_name_p (wv->name))
635     {
636       item = [NSMenuItem separatorItem];
637       [self addItem: item];
638     }
639   else
640     {
641       NSString *title, *keyEq;
642       title = [NSString stringWithUTF8String: wv->name];
643       if (title == nil)
644         title = @"< ? >";  /* (get out in the open so we know about it) */
646       keyEq = [self parseKeyEquiv: wv->key];
647 #ifdef NS_IMPL_COCOA
648       /* OS X just ignores modifier strings longer than one character */
649       if (keyEquivModMask == 0)
650         title = [title stringByAppendingFormat: @" (%@)", keyEq];
651 #endif
653       item = [self addItemWithTitle: (NSString *)title
654                              action: @selector (menuDown:)
655                       keyEquivalent: keyEq];
656       [item setKeyEquivalentModifierMask: keyEquivModMask];
658       [item setEnabled: wv->enabled];
660       /* Draw radio buttons and tickboxes */
661       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
662                            wv->button_type == BUTTON_TYPE_RADIO))
663         [item setState: NSOnState];
664       else
665         [item setState: NSOffState];
667       [item setTag: (NSInteger)wv->call_data];
668     }
670   return item;
674 /* convenience */
675 -(void)clear
677   int n;
679   for (n = [self numberOfItems]-1; n >= 0; n--)
680     {
681       NSMenuItem *item = [self itemAtIndex: n];
682       NSString *title = [item title];
683       if ([ns_app_name isEqualToString: title]
684           && ![item isSeparatorItem])
685         continue;
686       [self removeItemAtIndex: n];
687     }
691 - (void)fillWithWidgetValue: (void *)wvptr
693   [self fillWithWidgetValue: wvptr frame: (struct frame *)nil];
696 - (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f
698   widget_value *wv = (widget_value *)wvptr;
700   /* clear existing contents */
701   [self setMenuChangedMessagesEnabled: NO];
702   [self clear];
704   /* add new contents */
705   for (; wv != NULL; wv = wv->next)
706     {
707       NSMenuItem *item = [self addItemWithWidgetValue: wv];
709       if (wv->contents)
710         {
711           EmacsMenu *submenu;
713           if (f)
714             submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f];
715           else
716             submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
718           [self setSubmenu: submenu forItem: item];
719           [submenu fillWithWidgetValue: wv->contents];
720           [submenu release];
721           [item setAction: (SEL)nil];
722         }
723     }
725   [self setMenuChangedMessagesEnabled: YES];
726 #ifdef NS_IMPL_GNUSTEP
727   if ([[self window] isVisible])
728     [self sizeToFit];
729 #endif
733 /* adds an empty submenu and returns it */
734 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
736   NSString *titleStr = [NSString stringWithUTF8String: title];
737   NSMenuItem *item = [self addItemWithTitle: titleStr
738                                      action: (SEL)nil /*@selector (menuDown:) */
739                               keyEquivalent: @""];
740   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
741   [self setSubmenu: submenu forItem: item];
742   [submenu release];
743   return submenu;
746 /* run a menu in popup mode */
747 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
748                  keymaps: (bool)keymaps
750   EmacsView *view = FRAME_NS_VIEW (f);
751   NSEvent *e, *event;
752   long retVal;
754 /*   p = [view convertPoint:p fromView: nil]; */
755   p.y = NSHeight ([view frame]) - p.y;
756   e = [[view window] currentEvent];
757    event = [NSEvent mouseEventWithType: NSRightMouseDown
758                               location: p
759                          modifierFlags: 0
760                              timestamp: [e timestamp]
761                           windowNumber: [[view window] windowNumber]
762                                context: [e context]
763                            eventNumber: 0/*[e eventNumber] */
764                             clickCount: 1
765                               pressure: 0];
767   context_menu_value = -1;
768   [NSMenu popUpContextMenu: self withEvent: event forView: view];
769   retVal = context_menu_value;
770   context_menu_value = 0;
771   return retVal > 0
772       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
773       : Qnil;
776 @end  /* EmacsMenu */
780 /* ==========================================================================
782     Context Menu: implementing functions
784    ========================================================================== */
786 Lisp_Object
787 ns_menu_show (struct frame *f, int x, int y, int menuflags,
788               Lisp_Object title, const char **error)
790   EmacsMenu *pmenu;
791   NSPoint p;
792   Lisp_Object tem;
793   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
794   widget_value *wv, *first_wv = 0;
795   bool keymaps = (menuflags & MENU_KEYMAPS);
797   NSTRACE ("ns_menu_show");
799   block_input ();
801   p.x = x; p.y = y;
803   /* now parse stage 2 as in ns_update_menubar */
804   wv = make_widget_value ("contextmenu", NULL, true, Qnil);
805   wv->button_type = BUTTON_TYPE_NONE;
806   first_wv = wv;
808 #if 0
809   /* FIXME: a couple of one-line differences prevent reuse */
810   wv = digest_single_submenu (0, menu_items_used, 0);
811 #else
812   {
813   widget_value *save_wv = 0, *prev_wv = 0;
814   widget_value **submenu_stack
815     = alloca (menu_items_used * sizeof *submenu_stack);
816 /*   Lisp_Object *subprefix_stack
817        = alloca (menu_items_used * sizeof *subprefix_stack); */
818   int submenu_depth = 0;
819   int first_pane = 1;
820   int i;
822   /* Loop over all panes and items, filling in the tree.  */
823   i = 0;
824   while (i < menu_items_used)
825     {
826       if (EQ (AREF (menu_items, i), Qnil))
827         {
828           submenu_stack[submenu_depth++] = save_wv;
829           save_wv = prev_wv;
830           prev_wv = 0;
831           first_pane = 1;
832           i++;
833         }
834       else if (EQ (AREF (menu_items, i), Qlambda))
835         {
836           prev_wv = save_wv;
837           save_wv = submenu_stack[--submenu_depth];
838           first_pane = 0;
839           i++;
840         }
841       else if (EQ (AREF (menu_items, i), Qt)
842                && submenu_depth != 0)
843         i += MENU_ITEMS_PANE_LENGTH;
844       /* Ignore a nil in the item list.
845          It's meaningful only for dialog boxes.  */
846       else if (EQ (AREF (menu_items, i), Qquote))
847         i += 1;
848       else if (EQ (AREF (menu_items, i), Qt))
849         {
850           /* Create a new pane.  */
851           Lisp_Object pane_name, prefix;
852           const char *pane_string;
854           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
855           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
857 #ifndef HAVE_MULTILINGUAL_MENU
858           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
859             {
860               pane_name = ENCODE_MENU_STRING (pane_name);
861               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
862             }
863 #endif
864           pane_string = (NILP (pane_name)
865                          ? "" : SSDATA (pane_name));
866           /* If there is just one top-level pane, put all its items directly
867              under the top-level menu.  */
868           if (menu_items_n_panes == 1)
869             pane_string = "";
871           /* If the pane has a meaningful name,
872              make the pane a top-level menu item
873              with its items as a submenu beneath it.  */
874           if (!keymaps && strcmp (pane_string, ""))
875             {
876               wv = make_widget_value (pane_string, NULL, true, Qnil);
877               if (save_wv)
878                 save_wv->next = wv;
879               else
880                 first_wv->contents = wv;
881               if (keymaps && !NILP (prefix))
882                 wv->name++;
883               wv->button_type = BUTTON_TYPE_NONE;
884               save_wv = wv;
885               prev_wv = 0;
886             }
887           else if (first_pane)
888             {
889               save_wv = wv;
890               prev_wv = 0;
891             }
892           first_pane = 0;
893           i += MENU_ITEMS_PANE_LENGTH;
894         }
895       else
896         {
897           /* Create a new item within current pane.  */
898           Lisp_Object item_name, enable, descrip, def, type, selected, help;
899           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
900           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
901           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
902           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
903           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
904           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
905           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
907 #ifndef HAVE_MULTILINGUAL_MENU
908           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
909             {
910               item_name = ENCODE_MENU_STRING (item_name);
911               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
912             }
914           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
915             {
916               descrip = ENCODE_MENU_STRING (descrip);
917               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
918             }
919 #endif /* not HAVE_MULTILINGUAL_MENU */
921           wv = make_widget_value (SSDATA (item_name), NULL, !NILP (enable),
922                                   STRINGP (help) ? help : Qnil);
923           if (prev_wv)
924             prev_wv->next = wv;
925           else
926             save_wv->contents = wv;
927           if (!NILP (descrip))
928             wv->key = SSDATA (descrip);
929           /* If this item has a null value,
930              make the call_data null so that it won't display a box
931              when the mouse is on it.  */
932           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
934           if (NILP (type))
935             wv->button_type = BUTTON_TYPE_NONE;
936           else if (EQ (type, QCtoggle))
937             wv->button_type = BUTTON_TYPE_TOGGLE;
938           else if (EQ (type, QCradio))
939             wv->button_type = BUTTON_TYPE_RADIO;
940           else
941             emacs_abort ();
943           wv->selected = !NILP (selected);
945           prev_wv = wv;
947           i += MENU_ITEMS_ITEM_LENGTH;
948         }
949     }
950   }
951 #endif
953   if (!NILP (title))
954     {
955       widget_value *wv_title;
956       widget_value *wv_sep = make_widget_value ("--", NULL, false, Qnil);
958       /* Maybe replace this separator with a bitmap or owner-draw item
959          so that it looks better.  Having two separators looks odd.  */
960       wv_sep->next = first_wv->contents;
962 #ifndef HAVE_MULTILINGUAL_MENU
963       if (STRING_MULTIBYTE (title))
964         title = ENCODE_MENU_STRING (title);
965 #endif
966       wv_title = make_widget_value (SSDATA (title), NULL, false, Qnil);
967       wv_title->button_type = BUTTON_TYPE_NONE;
968       wv_title->next = wv_sep;
969       first_wv->contents = wv_title;
970     }
972   pmenu = [[EmacsMenu alloc] initWithTitle:
973                                [NSString stringWithUTF8String: SSDATA (title)]];
974   [pmenu fillWithWidgetValue: first_wv->contents];
975   free_menubar_widget_value_tree (first_wv);
976   unbind_to (specpdl_count, Qnil);
978   popup_activated_flag = 1;
979   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
980   popup_activated_flag = 0;
981   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
983   unblock_input ();
984   return tem;
988 /* ==========================================================================
990     Toolbar: externally-called functions
992    ========================================================================== */
994 void
995 free_frame_tool_bar (struct frame *f)
996 /* --------------------------------------------------------------------------
997     Under NS we just hide the toolbar until it might be needed again.
998    -------------------------------------------------------------------------- */
1000   EmacsView *view = FRAME_NS_VIEW (f);
1002   NSTRACE ("free_frame_tool_bar");
1004   block_input ();
1005   view->wait_for_tool_bar = NO;
1007   FRAME_TOOLBAR_HEIGHT (f) = 0;
1009   /* Note: This trigger an animation, which calls windowDidResize
1010      repeatedly. */
1011   f->output_data.ns->in_animation = 1;
1012   [[view toolbar] setVisible: NO];
1013   f->output_data.ns->in_animation = 0;
1015   unblock_input ();
1018 void
1019 update_frame_tool_bar (struct frame *f)
1020 /* --------------------------------------------------------------------------
1021     Update toolbar contents
1022    -------------------------------------------------------------------------- */
1024   int i, k = 0;
1025   EmacsView *view = FRAME_NS_VIEW (f);
1026   NSWindow *window = [view window];
1027   EmacsToolbar *toolbar = [view toolbar];
1028   int oldh;
1030   NSTRACE ("update_frame_tool_bar");
1032   if (view == nil || toolbar == nil) return;
1033   block_input ();
1035   oldh = FRAME_TOOLBAR_HEIGHT (f);
1037 #ifdef NS_IMPL_COCOA
1038   [toolbar clearActive];
1039 #else
1040   [toolbar clearAll];
1041 #endif
1043   /* update EmacsToolbar as in GtkUtils, build items list */
1044   for (i = 0; i < f->n_tool_bar_items; ++i)
1045     {
1046 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1047                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1049       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1050       int idx;
1051       ptrdiff_t img_id;
1052       struct image *img;
1053       Lisp_Object image;
1054       Lisp_Object helpObj;
1055       const char *helpText;
1057       /* Check if this is a separator.  */
1058       if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1059         {
1060           /* Skip separators.  Newer OSX don't show them, and on GNUstep they
1061              are wide as a button, thus overflowing the toolbar most of
1062              the time.  */
1063           continue;
1064         }
1066       /* If image is a vector, choose the image according to the
1067          button state.  */
1068       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1069       if (VECTORP (image))
1070         {
1071           /* NS toolbar auto-computes disabled and selected images */
1072           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1073           eassert (ASIZE (image) >= idx);
1074           image = AREF (image, idx);
1075         }
1076       else
1077         {
1078           idx = -1;
1079         }
1080       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1081       if (NILP (helpObj))
1082         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1083       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1085       /* Ignore invalid image specifications.  */
1086       if (!valid_image_p (image))
1087         {
1088           /* Don't log anything, GNUS makes invalid images all the time.  */
1089           continue;
1090         }
1092       img_id = lookup_image (f, image);
1093       img = IMAGE_FROM_ID (f, img_id);
1094       prepare_image_for_display (f, img);
1096       if (img->load_failed_p || img->pixmap == nil)
1097         {
1098           NSLog (@"Could not prepare toolbar image for display.");
1099           continue;
1100         }
1102       [toolbar addDisplayItemWithImage: img->pixmap
1103                                    idx: k++
1104                                    tag: i
1105                               helpText: helpText
1106                                enabled: enabled_p];
1107 #undef TOOLPROP
1108     }
1110   if (![toolbar isVisible])
1111     {
1112       f->output_data.ns->in_animation = 1;
1113       [toolbar setVisible: YES];
1114       f->output_data.ns->in_animation = 0;
1115     }
1117 #ifdef NS_IMPL_COCOA
1118   if ([toolbar changed])
1119     {
1120       /* inform app that toolbar has changed */
1121       NSDictionary *dict = [toolbar configurationDictionary];
1122       NSMutableDictionary *newDict = [dict mutableCopy];
1123       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1124       id key;
1125       while ((key = [keys nextObject]) != nil)
1126         {
1127           NSObject *val = [dict objectForKey: key];
1128           if ([val isKindOfClass: [NSArray class]])
1129             {
1130               [newDict setObject:
1131                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1132                           forKey: key];
1133               break;
1134             }
1135         }
1136       [toolbar setConfigurationFromDictionary: newDict];
1137       [newDict release];
1138     }
1139 #endif
1141   FRAME_TOOLBAR_HEIGHT (f) =
1142     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1143     - FRAME_NS_TITLEBAR_HEIGHT (f);
1144   if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1145     FRAME_TOOLBAR_HEIGHT (f) = 0;
1147   if (oldh != FRAME_TOOLBAR_HEIGHT (f))
1148     [view updateFrameSize:YES];
1149   if (view->wait_for_tool_bar && FRAME_TOOLBAR_HEIGHT (f) > 0)
1150     {
1151       view->wait_for_tool_bar = NO;
1152       [view setNeedsDisplay: YES];
1153     }
1155   unblock_input ();
1159 /* ==========================================================================
1161     Toolbar: class implementation
1163    ========================================================================== */
1165 @implementation EmacsToolbar
1167 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1169   NSTRACE ("[EmacsToolbar initForView: withIdentifier:]");
1171   self = [super initWithIdentifier: identifier];
1172   emacsView = view;
1173   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1174   [self setSizeMode: NSToolbarSizeModeSmall];
1175   [self setDelegate: self];
1176   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1177   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1178   prevIdentifiers = nil;
1179   prevEnablement = enablement = 0L;
1180   return self;
1183 - (void)dealloc
1185   NSTRACE ("[EmacsToolbar dealloc]");
1187   [prevIdentifiers release];
1188   [activeIdentifiers release];
1189   [identifierToItem release];
1190   [super dealloc];
1193 - (void) clearActive
1195   NSTRACE ("[EmacsToolbar clearActive]");
1197   [prevIdentifiers release];
1198   prevIdentifiers = [activeIdentifiers copy];
1199   [activeIdentifiers removeAllObjects];
1200   prevEnablement = enablement;
1201   enablement = 0L;
1204 - (void) clearAll
1206   NSTRACE ("[EmacsToolbar clearAll]");
1208   [self clearActive];
1209   while ([[self items] count] > 0)
1210     [self removeItemAtIndex: 0];
1213 - (BOOL) changed
1215   NSTRACE ("[EmacsToolbar changed]");
1217   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1218     enablement == prevEnablement ? NO : YES;
1221 - (void) addDisplayItemWithImage: (EmacsImage *)img
1222                              idx: (int)idx
1223                              tag: (int)tag
1224                         helpText: (const char *)help
1225                          enabled: (BOOL)enabled
1227   NSTRACE ("[EmacsToolbar addDisplayItemWithImage: ...]");
1229   /* 1) come up w/identifier */
1230   NSString *identifier
1231     = [NSString stringWithFormat: @"%lu", (unsigned long)[img hash]];
1232   [activeIdentifiers addObject: identifier];
1234   /* 2) create / reuse item */
1235   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1236   if (item == nil)
1237     {
1238       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1239                autorelease];
1240       [item setImage: img];
1241       [item setToolTip: [NSString stringWithUTF8String: help]];
1242       [item setTarget: emacsView];
1243       [item setAction: @selector (toolbarClicked:)];
1244       [identifierToItem setObject: item forKey: identifier];
1245     }
1247 #ifdef NS_IMPL_GNUSTEP
1248   [self insertItemWithItemIdentifier: identifier atIndex: idx];
1249 #endif
1251   [item setTag: tag];
1252   [item setEnabled: enabled];
1254   /* 3) update state */
1255   enablement = (enablement << 1) | (enabled == YES);
1258 /* This overrides super's implementation, which automatically sets
1259    all items to enabled state (for some reason). */
1260 - (void)validateVisibleItems
1262   NSTRACE ("[EmacsToolbar validateVisibleItems]");
1266 /* delegate methods */
1268 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1269       itemForItemIdentifier: (NSString *)itemIdentifier
1270   willBeInsertedIntoToolbar: (BOOL)flag
1272   NSTRACE ("[EmacsToolbar toolbar: ...]");
1274   /* look up NSToolbarItem by identifier and return... */
1275   return [identifierToItem objectForKey: itemIdentifier];
1278 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1280   NSTRACE ("[EmacsToolbar toolbarDefaultItemIdentifiers:]");
1282   /* return entire set.. */
1283   return activeIdentifiers;
1286 /* for configuration palette (not yet supported) */
1287 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1289   NSTRACE ("[EmacsToolbar toolbarAllowedItemIdentifiers:]");
1291   /* return entire set... */
1292   return activeIdentifiers;
1293   //return [identifierToItem allKeys];
1296 - (void)setVisible:(BOOL)shown
1298   NSTRACE ("[EmacsToolbar setVisible:%d]", shown);
1300   [super setVisible:shown];
1304 /* optional and unneeded */
1305 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1306 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1307 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1309 @end  /* EmacsToolbar */
1313 /* ==========================================================================
1315     Tooltip: class implementation
1317    ========================================================================== */
1319 /* Needed because NeXTstep does not provide enough control over tooltip
1320    display. */
1321 @implementation EmacsTooltip
1323 - init
1325   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1326                                             blue: 0.792 alpha: 0.95];
1327   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1328   NSFont *sfont = [font screenFont];
1329   int height = [sfont ascender] - [sfont descender];
1330 /*[font boundingRectForFont].size.height; */
1331   NSRect r = NSMakeRect (0, 0, 100, height+6);
1333   textField = [[NSTextField alloc] initWithFrame: r];
1334   [textField setFont: font];
1335   [textField setBackgroundColor: col];
1337   [textField setEditable: NO];
1338   [textField setSelectable: NO];
1339   [textField setBordered: NO];
1340   [textField setBezeled: NO];
1341   [textField setDrawsBackground: YES];
1343   win = [[NSWindow alloc]
1344             initWithContentRect: [textField frame]
1345                       styleMask: 0
1346                         backing: NSBackingStoreBuffered
1347                           defer: YES];
1348   [win setHasShadow: YES];
1349   [win setReleasedWhenClosed: NO];
1350   [win setDelegate: self];
1351   [[win contentView] addSubview: textField];
1352 /*  [win setBackgroundColor: col]; */
1353   [win setOpaque: NO];
1355   return self;
1358 - (void) dealloc
1360   [win close];
1361   [win release];
1362   [textField release];
1363   [super dealloc];
1366 - (void) setText: (char *)text
1368   NSString *str = [NSString stringWithUTF8String: text];
1369   NSRect r  = [textField frame];
1370   NSSize tooltipDims;
1372   [textField setStringValue: str];
1373   tooltipDims = [[textField cell] cellSize];
1375   r.size.width = tooltipDims.width;
1376   r.size.height = tooltipDims.height;
1377   [textField setFrame: r];
1380 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1382   NSRect wr = [win frame];
1384   wr.origin = NSMakePoint (x, y);
1385   wr.size = [textField frame].size;
1387   [win setFrame: wr display: YES];
1388   [win setLevel: NSPopUpMenuWindowLevel];
1389   [win orderFront: self];
1390   [win display];
1391   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1392                                          selector: @selector (hide)
1393                                          userInfo: nil repeats: NO];
1394   [timer retain];
1397 - (void) hide
1399   [win close];
1400   if (timer != nil)
1401     {
1402       if ([timer isValid])
1403         [timer invalidate];
1404       [timer release];
1405       timer = nil;
1406     }
1409 - (BOOL) isActive
1411   return timer != nil;
1414 - (NSRect) frame
1416   return [textField frame];
1419 @end  /* EmacsTooltip */
1423 /* ==========================================================================
1425     Popup Dialog: implementing functions
1427    ========================================================================== */
1429 struct Popdown_data
1431   NSAutoreleasePool *pool;
1432   EmacsDialogPanel *dialog;
1435 static void
1436 pop_down_menu (void *arg)
1438   struct Popdown_data *unwind_data = arg;
1440   block_input ();
1441   if (popup_activated_flag)
1442     {
1443       EmacsDialogPanel *panel = unwind_data->dialog;
1444       popup_activated_flag = 0;
1445       [panel close];
1446       [unwind_data->pool release];
1447       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1448     }
1450   xfree (unwind_data);
1451   unblock_input ();
1455 Lisp_Object
1456 ns_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
1458   id dialog;
1459   Lisp_Object tem, title;
1460   NSPoint p;
1461   BOOL isQ;
1462   NSAutoreleasePool *pool;
1464   NSTRACE ("ns_popup_dialog");
1466   isQ = NILP (header);
1468   check_window_system (f);
1470   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1471   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1473   title = Fcar (contents);
1474   CHECK_STRING (title);
1476   if (NILP (Fcar (Fcdr (contents))))
1477     /* No buttons specified, add an "Ok" button so users can pop down
1478        the dialog.  */
1479     contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1481   block_input ();
1482   pool = [[NSAutoreleasePool alloc] init];
1483   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1484                                            isQuestion: isQ];
1486   {
1487     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1488     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1490     unwind_data->pool = pool;
1491     unwind_data->dialog = dialog;
1493     record_unwind_protect_ptr (pop_down_menu, unwind_data);
1494     popup_activated_flag = 1;
1495     tem = [dialog runDialogAt: p];
1496     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1497   }
1499   unblock_input ();
1501   return tem;
1505 /* ==========================================================================
1507     Popup Dialog: class implementation
1509    ========================================================================== */
1511 @interface FlippedView : NSView
1514 @end
1516 @implementation FlippedView
1517 - (BOOL)isFlipped
1519   return YES;
1521 @end
1523 @implementation EmacsDialogPanel
1525 #define SPACER          8.0
1526 #define ICONSIZE        64.0
1527 #define TEXTHEIGHT      20.0
1528 #define MINCELLWIDTH    90.0
1530 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1531               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1533   NSSize spacing = {SPACER, SPACER};
1534   NSRect area;
1535   id cell;
1536   NSImageView *imgView;
1537   FlippedView *contentView;
1538   NSImage *img;
1540   dialog_return   = Qundefined;
1541   button_values   = NULL;
1542   area.origin.x   = 3*SPACER;
1543   area.origin.y   = 2*SPACER;
1544   area.size.width = ICONSIZE;
1545   area.size.height= ICONSIZE;
1546   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1547 #ifdef NS_IMPL_COCOA
1548 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
1549   [img setScalesWhenResized: YES];
1550 #endif
1551 #endif
1552   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1553   imgView = [[NSImageView alloc] initWithFrame: area];
1554   [imgView setImage: img];
1555   [imgView setEditable: NO];
1556   [img autorelease];
1557   [imgView autorelease];
1559   aStyle = NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask;
1560   flag = YES;
1561   rows = 0;
1562   cols = 1;
1563   [super initWithContentRect: contentRect styleMask: aStyle
1564                      backing: backingType defer: flag];
1565   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1566   [contentView autorelease];
1568   [self setContentView: contentView];
1570   [[self contentView] setAutoresizesSubviews: YES];
1572   [[self contentView] addSubview: imgView];
1573   [self setTitle: @""];
1575   area.origin.x   += ICONSIZE+2*SPACER;
1576 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1577   area.size.width = 400;
1578   area.size.height= TEXTHEIGHT;
1579   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1580   [[self contentView] addSubview: command];
1581   [command setStringValue: ns_app_name];
1582   [command setDrawsBackground: NO];
1583   [command setBezeled: NO];
1584   [command setSelectable: NO];
1585   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1587 /*  area.origin.x   = ICONSIZE+2*SPACER;
1588   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1589   area.size.width = 400;
1590   area.size.height= 2;
1591   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1592   [[self contentView] addSubview: tem];
1593   [tem setTitlePosition: NSNoTitle];
1594   [tem setAutoresizingMask: NSViewWidthSizable];*/
1596 /*  area.origin.x = ICONSIZE+2*SPACER; */
1597   area.origin.y += TEXTHEIGHT+SPACER;
1598   area.size.width = 400;
1599   area.size.height= TEXTHEIGHT;
1600   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1601   [[self contentView] addSubview: title];
1602   [title setDrawsBackground: NO];
1603   [title setBezeled: NO];
1604   [title setSelectable: NO];
1605   [title setFont: [NSFont systemFontOfSize: 11.0]];
1607   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1608   [cell setBordered: NO];
1609   [cell setEnabled: NO];
1610   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1611   [cell setBezelStyle: NSRoundedBezelStyle];
1613   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1614                                       mode: NSHighlightModeMatrix
1615                                  prototype: cell
1616                               numberOfRows: 0
1617                            numberOfColumns: 1];
1618   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1619                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1620   [matrix setIntercellSpacing: spacing];
1621   [matrix autorelease];
1623   [[self contentView] addSubview: matrix];
1624   [self setOneShot: YES];
1625   [self setReleasedWhenClosed: YES];
1626   [self setHidesOnDeactivate: YES];
1627   return self;
1631 - (BOOL)windowShouldClose: (id)sender
1633   window_closed = YES;
1634   [NSApp stop:self];
1635   return NO;
1638 - (void)dealloc
1640   xfree (button_values);
1641   [super dealloc];
1644 - (void)process_dialog: (Lisp_Object) list
1646   Lisp_Object item, lst = list;
1647   int row = 0;
1648   int buttons = 0, btnnr = 0;
1650   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1651     {
1652       item = XCAR (list);
1653       if (XTYPE (item) == Lisp_Cons)
1654         ++buttons;
1655     }
1657   if (buttons > 0)
1658     button_values = xmalloc (buttons * sizeof *button_values);
1660   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1661     {
1662       item = XCAR (list);
1663       if (XTYPE (item) == Lisp_String)
1664         {
1665           [self addString: SSDATA (item) row: row++];
1666         }
1667       else if (XTYPE (item) == Lisp_Cons)
1668         {
1669           button_values[btnnr] = XCDR (item);
1670           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1671           ++btnnr;
1672         }
1673       else if (NILP (item))
1674         {
1675           [self addSplit];
1676           row = 0;
1677         }
1678     }
1682 - (void)addButton: (char *)str value: (int)tag row: (int)row
1684   id cell;
1686   if (row >= rows)
1687     {
1688       [matrix addRow];
1689       rows++;
1690     }
1691   cell = [matrix cellAtRow: row column: cols-1];
1692   [cell setTarget: self];
1693   [cell setAction: @selector (clicked: )];
1694   [cell setTitle: [NSString stringWithUTF8String: str]];
1695   [cell setTag: tag];
1696   [cell setBordered: YES];
1697   [cell setEnabled: YES];
1701 - (void)addString: (char *)str row: (int)row
1703   id cell;
1705   if (row >= rows)
1706     {
1707       [matrix addRow];
1708       rows++;
1709     }
1710   cell = [matrix cellAtRow: row column: cols-1];
1711   [cell setTitle: [NSString stringWithUTF8String: str]];
1712   [cell setBordered: YES];
1713   [cell setEnabled: NO];
1717 - (void)addSplit
1719   [matrix addColumn];
1720   cols++;
1724 - (void)clicked: sender
1726   NSArray *sellist = nil;
1727   EMACS_INT seltag;
1729   sellist = [sender selectedCells];
1730   if ([sellist count] < 1)
1731     return;
1733   seltag = [[sellist objectAtIndex: 0] tag];
1734   dialog_return = button_values[seltag];
1735   [NSApp stop:self];
1739 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1741   Lisp_Object head;
1742   [super init];
1744   if (XTYPE (contents) == Lisp_Cons)
1745     {
1746       head = Fcar (contents);
1747       [self process_dialog: Fcdr (contents)];
1748     }
1749   else
1750     head = contents;
1752   if (XTYPE (head) == Lisp_String)
1753       [title setStringValue:
1754                  [NSString stringWithUTF8String: SSDATA (head)]];
1755   else if (isQ == YES)
1756       [title setStringValue: @"Question"];
1757   else
1758       [title setStringValue: @"Information"];
1760   {
1761     int i;
1762     NSRect r, s, t;
1764     if (cols == 1 && rows > 1)  /* Never told where to split */
1765       {
1766         [matrix addColumn];
1767         for (i = 0; i < rows/2; i++)
1768           {
1769             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1770                       atRow: i column: 1];
1771             [matrix removeRow: (rows+1)/2];
1772           }
1773       }
1775     [matrix sizeToFit];
1776     {
1777       NSSize csize = [matrix cellSize];
1778       if (csize.width < MINCELLWIDTH)
1779         {
1780           csize.width = MINCELLWIDTH;
1781           [matrix setCellSize: csize];
1782           [matrix sizeToCells];
1783         }
1784     }
1786     [title sizeToFit];
1787     [command sizeToFit];
1789     t = [matrix frame];
1790     r = [title frame];
1791     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1792       {
1793         t.origin.x   = r.origin.x;
1794         t.size.width = r.size.width;
1795       }
1796     r = [command frame];
1797     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1798       {
1799         t.origin.x   = r.origin.x;
1800         t.size.width = r.size.width;
1801       }
1803     r = [self frame];
1804     s = [(NSView *)[self contentView] frame];
1805     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1806     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1807     [self setFrame: r display: NO];
1808   }
1810   return self;
1815 - (void)timeout_handler: (NSTimer *)timedEntry
1817   NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1818                             location: NSMakePoint (0, 0)
1819                        modifierFlags: 0
1820                            timestamp: 0
1821                         windowNumber: [[NSApp mainWindow] windowNumber]
1822                              context: [NSApp context]
1823                              subtype: 0
1824                                data1: 0
1825                                data2: 0];
1827   timer_fired = YES;
1828   /* We use sto because stopModal/abortModal out of the main loop does not
1829      seem to work in 10.6.  But as we use stop we must send a real event so
1830      the stop is seen and acted upon.  */
1831   [NSApp stop:self];
1832   [NSApp postEvent: nxev atStart: NO];
1835 - (Lisp_Object)runDialogAt: (NSPoint)p
1837   Lisp_Object ret = Qundefined;
1839   while (popup_activated_flag)
1840     {
1841       NSTimer *tmo = nil;
1842       struct timespec next_time = timer_check ();
1844       if (timespec_valid_p (next_time))
1845         {
1846           double time = timespectod (next_time);
1847           tmo = [NSTimer timerWithTimeInterval: time
1848                                         target: self
1849                                       selector: @selector (timeout_handler:)
1850                                       userInfo: 0
1851                                        repeats: NO];
1852           [[NSRunLoop currentRunLoop] addTimer: tmo
1853                                        forMode: NSModalPanelRunLoopMode];
1854         }
1855       timer_fired = NO;
1856       dialog_return = Qundefined;
1857       [NSApp runModalForWindow: self];
1858       ret = dialog_return;
1859       if (! timer_fired)
1860         {
1861           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1862           break;
1863         }
1864     }
1866   if (EQ (ret, Qundefined) && window_closed)
1867     /* Make close button pressed equivalent to C-g.  */
1868     Fsignal (Qquit, Qnil);
1870   return ret;
1873 @end
1876 /* ==========================================================================
1878     Lisp definitions
1880    ========================================================================== */
1882 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1883        doc: /* Cause the NS menu to be re-calculated.  */)
1884      (void)
1886   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1887   return Qnil;
1891 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1892        doc: /* Return t if a menu or popup dialog is active.  */)
1893      (void)
1895   return popup_activated () ? Qt : Qnil;
1898 /* ==========================================================================
1900     Lisp interface declaration
1902    ========================================================================== */
1904 void
1905 syms_of_nsmenu (void)
1907 #ifndef NS_IMPL_COCOA
1908   /* Don't know how to keep track of this in Next/Open/GNUstep.  Always
1909      update menus there.  */
1910   trackingMenu = 1;
1911 #endif
1912   defsubr (&Sns_reset_menu);
1913   defsubr (&Smenu_or_popup_active_p);
1915   DEFSYM (Qdebug_on_next_call, "debug-on-next-call");