Remove old cl-assert calls in 'newline'
[emacs.git] / src / nsmenu.m
blob6ef7b60dc24d5bb47bf54f4078cb072e4cdaa3c0
1 /* NeXT/Open/GNUstep and macOS Cocoa menu and toolbar module.
2    Copyright (C) 2007-2017 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 <https://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 *svcsMenu;
57 /* Nonzero means a menu is currently active.  */
58 static int popup_activated_flag;
60 /* Nonzero means we are tracking and updating menus.  */
61 static int trackingMenu;
64 /* NOTE: toolbar implementation is at end,
65   following complete menu implementation. */
68 /* ==========================================================================
70     Menu: Externally-called functions
72    ========================================================================== */
75 /* Supposed to discard menubar and free storage.  Since we share the
76    menubar among frames and update its context for the focused window,
77    there is nothing to do here. */
78 void
79 free_frame_menubar (struct frame *f)
81   return;
85 int
86 popup_activated (void)
88   return popup_activated_flag;
92 /* --------------------------------------------------------------------------
93     Update menubar.  Three cases:
94     1) ! deep_p, submenu = nil: Fresh switch onto a frame -- either set up
95        just top-level menu strings (macOS), or goto case (2) (GNUstep).
96     2) deep_p, submenu = nil: Recompute all submenus.
97     3) deep_p, submenu = non-nil: Update contents of a single submenu.
98    -------------------------------------------------------------------------- */
99 static void
100 ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu)
102   NSAutoreleasePool *pool;
103   id menu = [NSApp mainMenu];
104   static EmacsMenu *last_submenu = nil;
105   BOOL needsSet = NO;
106   bool owfi;
107   Lisp_Object items;
108   widget_value *wv, *first_wv, *prev_wv = 0;
109   int i;
111 #if NSMENUPROFILE
112   struct timeb tb;
113   long t;
114 #endif
116   NSTRACE ("ns_update_menubar");
118   if (f != SELECTED_FRAME ())
119       return;
120   XSETFRAME (Vmenu_updating_frame, f);
121 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
123   block_input ();
124   pool = [[NSAutoreleasePool alloc] init];
126   /* Menu may have been created automatically; if so, discard it. */
127   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
128     {
129       [menu release];
130       menu = nil;
131     }
133   if (menu == nil)
134     {
135       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
136       needsSet = YES;
137     }
139 #if NSMENUPROFILE
140   ftime (&tb);
141   t = -(1000*tb.time+tb.millitm);
142 #endif
144 #ifdef NS_IMPL_GNUSTEP
145   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
146 #endif
148   if (deep_p)
149     {
150       /* Fully parse one or more of the submenus. */
151       int n = 0;
152       int *submenu_start, *submenu_end;
153       bool *submenu_top_level_items;
154       int *submenu_n_panes;
155       struct buffer *prev = current_buffer;
156       Lisp_Object buffer;
157       ptrdiff_t specpdl_count = SPECPDL_INDEX ();
158       int previous_menu_items_used = f->menu_bar_items_used;
159       Lisp_Object *previous_items
160         = alloca (previous_menu_items_used * sizeof *previous_items);
162       /* lisp preliminaries */
163       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->contents;
164       specbind (Qinhibit_quit, Qt);
165       specbind (Qdebug_on_next_call, Qnil);
166       record_unwind_save_match_data ();
167       if (NILP (Voverriding_local_map_menu_flag))
168         {
169           specbind (Qoverriding_terminal_local_map, Qnil);
170           specbind (Qoverriding_local_map, Qnil);
171         }
172       set_buffer_internal_1 (XBUFFER (buffer));
174       /* TODO: for some reason this is not needed in other terms,
175            but some menu updates call Info-extract-pointer which causes
176            abort-on-error if waiting-for-input.  Needs further investigation. */
177       owfi = waiting_for_input;
178       waiting_for_input = 0;
180       /* lucid hook and possible reset */
181       safe_run_hooks (Qactivate_menubar_hook);
182       if (! NILP (Vlucid_menu_bar_dirty_flag))
183         call0 (Qrecompute_lucid_menubar);
184       safe_run_hooks (Qmenu_bar_update_hook);
185       fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
187       /* Now ready to go */
188       items = FRAME_MENU_BAR_ITEMS (f);
190       /* Save the frame's previous menu bar contents data */
191       if (previous_menu_items_used)
192         memcpy (previous_items, aref_addr (f->menu_bar_vector, 0),
193                 previous_menu_items_used * sizeof (Lisp_Object));
195       /* parse stage 1: extract from lisp */
196       save_menu_items ();
198       menu_items = f->menu_bar_vector;
199       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
200       submenu_start = alloca (ASIZE (items) * sizeof *submenu_start);
201       submenu_end = alloca (ASIZE (items) * sizeof *submenu_end);
202       submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes);
203       submenu_top_level_items = alloca (ASIZE (items)
204                                         * sizeof *submenu_top_level_items);
205       init_menu_items ();
206       for (i = 0; i < ASIZE (items); i += 4)
207         {
208           Lisp_Object key, string, maps;
210           key = AREF (items, i);
211           string = AREF (items, i + 1);
212           maps = AREF (items, i + 2);
213           if (NILP (string))
214             break;
216           /* FIXME: we'd like to only parse the needed submenu, but this
217                was causing crashes in the _common parsing code.. need to make
218                sure proper initialization done.. */
219 /*        if (submenu && strcmp ([[submenu title] UTF8String], SSDATA (string)))
220              continue; */
222           submenu_start[i] = menu_items_used;
224           menu_items_n_panes = 0;
225           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
226           submenu_n_panes[i] = menu_items_n_panes;
227           submenu_end[i] = menu_items_used;
228           n++;
229         }
231       finish_menu_items ();
232       waiting_for_input = owfi;
235       if (submenu && n == 0)
236         {
237           /* should have found a menu for this one but didn't */
238           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
239                   [[submenu title] UTF8String]);
240           discard_menu_items ();
241           unbind_to (specpdl_count, Qnil);
242           [pool release];
243           unblock_input ();
244           return;
245         }
247       /* parse stage 2: insert into lucid 'widget_value' structures
248          [comments in other terms say not to evaluate lisp code here] */
249       wv = make_widget_value ("menubar", NULL, true, Qnil);
250       wv->button_type = BUTTON_TYPE_NONE;
251       first_wv = wv;
253       for (i = 0; i < 4*n; i += 4)
254         {
255           menu_items_n_panes = submenu_n_panes[i];
256           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
257                                       submenu_top_level_items[i]);
258           if (prev_wv)
259             prev_wv->next = wv;
260           else
261             first_wv->contents = wv;
262           /* Don't set wv->name here; GC during the loop might relocate it.  */
263           wv->enabled = 1;
264           wv->button_type = BUTTON_TYPE_NONE;
265           prev_wv = wv;
266         }
268       set_buffer_internal_1 (prev);
270       /* Compare the new menu items with previous, and leave off if no change */
271       /* FIXME: following other terms here, but seems like this should be
272            done before parse stage 2 above, since its results aren't used */
273       if (previous_menu_items_used
274           && (!submenu || (submenu && submenu == last_submenu))
275           && menu_items_used == previous_menu_items_used)
276         {
277           for (i = 0; i < previous_menu_items_used; i++)
278             /* FIXME: this ALWAYS fails on Buffers menu items.. something
279                  about their strings causes them to change every time, so we
280                  double-check failures */
281             if (!EQ (previous_items[i], AREF (menu_items, i)))
282               if (!(STRINGP (previous_items[i])
283                     && STRINGP (AREF (menu_items, i))
284                     && !strcmp (SSDATA (previous_items[i]),
285                                 SSDATA (AREF (menu_items, i)))))
286                   break;
287           if (i == previous_menu_items_used)
288             {
289               /* No change.. */
291 #if NSMENUPROFILE
292               ftime (&tb);
293               t += 1000*tb.time+tb.millitm;
294               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
295 #endif
297               free_menubar_widget_value_tree (first_wv);
298               discard_menu_items ();
299               unbind_to (specpdl_count, Qnil);
300               [pool release];
301               unblock_input ();
302               return;
303             }
304         }
305       /* The menu items are different, so store them in the frame */
306       /* FIXME: this is not correct for single-submenu case */
307       fset_menu_bar_vector (f, menu_items);
308       f->menu_bar_items_used = menu_items_used;
310       /* Calls restore_menu_items, etc., as they were outside */
311       unbind_to (specpdl_count, Qnil);
313       /* Parse stage 2a: now GC cannot happen during the lifetime of the
314          widget_value, so it's safe to store data from a Lisp_String */
315       wv = first_wv->contents;
316       for (i = 0; i < ASIZE (items); i += 4)
317         {
318           Lisp_Object string;
319           string = AREF (items, i + 1);
320           if (NILP (string))
321             break;
323           wv->name = SSDATA (string);
324           update_submenu_strings (wv->contents);
325           wv = wv->next;
326         }
328       /* Now, update the NS menu; if we have a submenu, use that, otherwise
329          create a new menu for each sub and fill it. */
330       if (submenu)
331         {
332           const char *submenuTitle = [[submenu title] UTF8String];
333           for (wv = first_wv->contents; wv; wv = wv->next)
334             {
335               if (!strcmp (submenuTitle, wv->name))
336                 {
337                   [submenu fillWithWidgetValue: wv->contents];
338                   last_submenu = submenu;
339                   break;
340                 }
341             }
342         }
343       else
344         {
345           [menu fillWithWidgetValue: first_wv->contents frame: f];
346         }
348     }
349   else
350     {
351       static int n_previous_strings = 0;
352       static char previous_strings[100][10];
353       static struct frame *last_f = NULL;
354       int n;
355       Lisp_Object string;
357       wv = make_widget_value ("menubar", NULL, true, Qnil);
358       wv->button_type = BUTTON_TYPE_NONE;
359       first_wv = wv;
361       /* Make widget-value tree w/ just the top level menu bar strings */
362       items = FRAME_MENU_BAR_ITEMS (f);
363       if (NILP (items))
364         {
365           free_menubar_widget_value_tree (first_wv);
366           [pool release];
367           unblock_input ();
368           return;
369         }
372       /* check if no change.. this mechanism is a bit rough, but ready */
373       n = ASIZE (items) / 4;
374       if (f == last_f && n_previous_strings == n)
375         {
376           for (i = 0; i<n; i++)
377             {
378               string = AREF (items, 4*i+1);
380               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
381                 continue;
382               if (NILP (string))
383                 {
384                   if (previous_strings[i][0])
385                     break;
386                   else
387                     continue;
388                 }
389               else if (memcmp (previous_strings[i], SDATA (string),
390                           min (10, SBYTES (string) + 1)))
391                 break;
392             }
394           if (i == n)
395             {
396               free_menubar_widget_value_tree (first_wv);
397               [pool release];
398               unblock_input ();
399               return;
400             }
401         }
403       [menu clear];
404       for (i = 0; i < ASIZE (items); i += 4)
405         {
406           string = AREF (items, i + 1);
407           if (NILP (string))
408             break;
410           if (n < 100)
411             memcpy (previous_strings[i/4], SDATA (string),
412                     min (10, SBYTES (string) + 1));
414           wv = make_widget_value (SSDATA (string), NULL, true, Qnil);
415           wv->button_type = BUTTON_TYPE_NONE;
416           wv->call_data = (void *) (intptr_t) (-1);
418 #ifdef NS_IMPL_COCOA
419           /* we'll update the real copy under app menu when time comes */
420           if (!strcmp ("Services", wv->name))
421             {
422               /* but we need to make sure it will update on demand */
423               [svcsMenu setFrame: f];
424             }
425           else
426 #endif
427           [menu addSubmenuWithTitle: wv->name forFrame: f];
429           if (prev_wv)
430             prev_wv->next = wv;
431           else
432             first_wv->contents = wv;
433           prev_wv = wv;
434         }
436       last_f = f;
437       if (n < 100)
438         n_previous_strings = n;
439       else
440         n_previous_strings = 0;
442     }
443   free_menubar_widget_value_tree (first_wv);
446 #if NSMENUPROFILE
447   ftime (&tb);
448   t += 1000*tb.time+tb.millitm;
449   fprintf (stderr, "Menu update took %ld msec.\n", t);
450 #endif
452   /* set main menu */
453   if (needsSet)
454     [NSApp setMainMenu: menu];
456   [pool release];
457   unblock_input ();
462 /* Main emacs core entry point for menubar menus: called to indicate that the
463    frame's menus have changed, and the *step representation should be updated
464    from Lisp. */
465 void
466 set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
468   ns_update_menubar (f, deep_p, nil);
471 void
472 x_activate_menubar (struct frame *f)
474 #ifdef NS_IMPL_COCOA
475   ns_update_menubar (f, true, nil);
476   ns_check_pending_open_menu ();
477 #endif
483 /* ==========================================================================
485     Menu: class implementation
487    ========================================================================== */
490 /* Menu that can define itself from Emacs "widget_value"s and will lazily
491    update itself when user clicked.  Based on Carbon/AppKit implementation
492    by Yamamoto Mitsuharu. */
493 @implementation EmacsMenu
495 /* override designated initializer */
496 - (instancetype)initWithTitle: (NSString *)title
498   frame = 0;
499   if ((self = [super initWithTitle: title]))
500     [self setAutoenablesItems: NO];
501   return self;
505 /* used for top-level */
506 - (instancetype)initWithTitle: (NSString *)title frame: (struct frame *)f
508   [self initWithTitle: title];
509   frame = f;
510 #ifdef NS_IMPL_COCOA
511   [self setDelegate: self];
512 #endif
513   return self;
517 - (void)setFrame: (struct frame *)f
519   frame = f;
522 #ifdef NS_IMPL_COCOA
523 -(void)trackingNotification:(NSNotification *)notification
525   /* Update menu in menuNeedsUpdate only while tracking menus.  */
526   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
527                   ? 1 : 0);
528   if (! trackingMenu) ns_check_menu_open (nil);
531 - (void)menuWillOpen:(NSMenu *)menu
533   ++trackingMenu;
535 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
536   // On 10.6 we get repeated calls, only the one for NSSystemDefined is "real".
537   if (
538 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
539       NSAppKitVersionNumber < NSAppKitVersionNumber10_7 &&
540 #endif
541       [[NSApp currentEvent] type] != NSEventTypeSystemDefined)
542     return;
543 #endif
545   /* When dragging from one menu to another, we get willOpen followed by didClose,
546      i.e. trackingMenu == 3 in willOpen and then 2 after didClose.
547      We have updated all menus, so avoid doing it when trackingMenu == 3.  */
548   if (trackingMenu == 2)
549     ns_check_menu_open (menu);
552 - (void)menuDidClose:(NSMenu *)menu
554   --trackingMenu;
557 #endif /* NS_IMPL_COCOA */
559 /* delegate method called when a submenu is being opened: run a 'deep' call
560    to set_frame_menubar */
561 - (void)menuNeedsUpdate: (NSMenu *)menu
563   if (!FRAME_LIVE_P (frame))
564     return;
566   /* Cocoa/Carbon will request update on every keystroke
567      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
568      since key equivalents are handled through emacs.
569      On Leopard, even keystroke events generate SystemDefined event.
570      Third-party applications that enhance mouse / trackpad
571      interaction, or also VNC/Remote Desktop will send events
572      of type AppDefined rather than SysDefined.
573      Menus will fail to show up if they haven't been initialized.
574      AppDefined events may lack timing data.
576      Thus, we rely on the didBeginTrackingNotification notification
577      as above to indicate the need for updates.
578      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
579      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
580   */
581   if (trackingMenu == 0)
582     return;
583 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
584 #ifdef NS_IMPL_GNUSTEP
585   /* Don't know how to do this for anything other than Mac OS X 10.5 and later.
586      This is wrong, as it might run Lisp code in the event loop.  */
587   ns_update_menubar (frame, true, self);
588 #endif
592 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
594   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
595       && FRAME_NS_VIEW (SELECTED_FRAME ()))
596     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
597   return YES;
601 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
602    into an accelerator string.  We are only able to display a single character
603    for an accelerator, together with an optional modifier combination.  (Under
604    Carbon more control was possible, but in Cocoa multi-char strings passed to
605    NSMenuItem get ignored.  For now we try to display a super-single letter
606    combo, and return the others as strings to be appended to the item title.
607    (This is signaled by setting keyEquivModMask to 0 for now.) */
608 -(NSString *)parseKeyEquiv: (const char *)key
610   const char *tpos = key;
611   keyEquivModMask = NSEventModifierFlagCommand;
613   if (!key || !strlen (key))
614     return @"";
616   while (*tpos == ' ' || *tpos == '(')
617     tpos++;
618   if ((*tpos == 's') && (*(tpos+1) == '-'))
619     {
620       return [NSString stringWithFormat: @"%c", tpos[2]];
621     }
622   keyEquivModMask = 0; /* signal */
623   return [NSString stringWithUTF8String: tpos];
627 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
629   NSMenuItem *item;
630   widget_value *wv = (widget_value *)wvptr;
632   if (menu_separator_name_p (wv->name))
633     {
634       item = [NSMenuItem separatorItem];
635       [self addItem: item];
636     }
637   else
638     {
639       NSString *title, *keyEq;
640       title = [NSString stringWithUTF8String: wv->name];
641       if (title == nil)
642         title = @"< ? >";  /* (get out in the open so we know about it) */
644       keyEq = [self parseKeyEquiv: wv->key];
645 #ifdef NS_IMPL_COCOA
646       /* macOS just ignores modifier strings longer than one character */
647       if (keyEquivModMask == 0)
648         title = [title stringByAppendingFormat: @" (%@)", keyEq];
649 #endif
651       item = [self addItemWithTitle: (NSString *)title
652                              action: @selector (menuDown:)
653                       keyEquivalent: keyEq];
654       [item setKeyEquivalentModifierMask: keyEquivModMask];
656       [item setEnabled: wv->enabled];
658       /* Draw radio buttons and tickboxes */
659       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
660                            wv->button_type == BUTTON_TYPE_RADIO))
661         [item setState: NSOnState];
662       else
663         [item setState: NSOffState];
665       [item setTag: (NSInteger)wv->call_data];
666     }
668   return item;
672 /* convenience */
673 -(void)clear
675   int n;
677   for (n = [self numberOfItems]-1; n >= 0; n--)
678     {
679       NSMenuItem *item = [self itemAtIndex: n];
680       NSString *title = [item title];
681       if ([ns_app_name isEqualToString: title]
682           && ![item isSeparatorItem])
683         continue;
684       [self removeItemAtIndex: n];
685     }
689 - (void)fillWithWidgetValue: (void *)wvptr
691   [self fillWithWidgetValue: wvptr frame: (struct frame *)nil];
694 - (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f
696   widget_value *wv = (widget_value *)wvptr;
698   /* clear existing contents */
699   [self clear];
701   /* add new contents */
702   for (; wv != NULL; wv = wv->next)
703     {
704       NSMenuItem *item = [self addItemWithWidgetValue: wv];
706       if (wv->contents)
707         {
708           EmacsMenu *submenu;
710           if (f)
711             submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f];
712           else
713             submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
715           [self setSubmenu: submenu forItem: item];
716           [submenu fillWithWidgetValue: wv->contents];
717           [submenu release];
718           [item setAction: (SEL)nil];
719         }
720     }
722 #ifdef NS_IMPL_GNUSTEP
723   if ([[self window] isVisible])
724     [self sizeToFit];
725 #endif
729 /* adds an empty submenu and returns it */
730 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
732   NSString *titleStr = [NSString stringWithUTF8String: title];
733   NSMenuItem *item = [self addItemWithTitle: titleStr
734                                      action: (SEL)nil /*@selector (menuDown:) */
735                               keyEquivalent: @""];
736   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
737   [self setSubmenu: submenu forItem: item];
738   [submenu release];
739   return submenu;
742 /* run a menu in popup mode */
743 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
744                  keymaps: (bool)keymaps
746   EmacsView *view = FRAME_NS_VIEW (f);
747   NSEvent *e, *event;
748   long retVal;
750 /*   p = [view convertPoint:p fromView: nil]; */
751   p.y = NSHeight ([view frame]) - p.y;
752   e = [[view window] currentEvent];
753    event = [NSEvent mouseEventWithType: NSEventTypeRightMouseDown
754                               location: p
755                          modifierFlags: 0
756                              timestamp: [e timestamp]
757                           windowNumber: [[view window] windowNumber]
758                                context: nil
759                            eventNumber: 0/*[e eventNumber] */
760                             clickCount: 1
761                               pressure: 0];
763   context_menu_value = -1;
764   [NSMenu popUpContextMenu: self withEvent: event forView: view];
765   retVal = context_menu_value;
766   context_menu_value = 0;
767   return retVal > 0
768       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
769       : Qnil;
772 @end  /* EmacsMenu */
776 /* ==========================================================================
778     Context Menu: implementing functions
780    ========================================================================== */
782 Lisp_Object
783 ns_menu_show (struct frame *f, int x, int y, int menuflags,
784               Lisp_Object title, const char **error)
786   EmacsMenu *pmenu;
787   NSPoint p;
788   Lisp_Object tem;
789   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
790   widget_value *wv, *first_wv = 0;
791   bool keymaps = (menuflags & MENU_KEYMAPS);
793   NSTRACE ("ns_menu_show");
795   block_input ();
797   p.x = x; p.y = y;
799   /* now parse stage 2 as in ns_update_menubar */
800   wv = make_widget_value ("contextmenu", NULL, true, Qnil);
801   wv->button_type = BUTTON_TYPE_NONE;
802   first_wv = wv;
804 #if 0
805   /* FIXME: a couple of one-line differences prevent reuse */
806   wv = digest_single_submenu (0, menu_items_used, 0);
807 #else
808   {
809   widget_value *save_wv = 0, *prev_wv = 0;
810   widget_value **submenu_stack
811     = alloca (menu_items_used * sizeof *submenu_stack);
812 /*   Lisp_Object *subprefix_stack
813        = alloca (menu_items_used * sizeof *subprefix_stack); */
814   int submenu_depth = 0;
815   int first_pane = 1;
816   int i;
818   /* Loop over all panes and items, filling in the tree.  */
819   i = 0;
820   while (i < menu_items_used)
821     {
822       if (EQ (AREF (menu_items, i), Qnil))
823         {
824           submenu_stack[submenu_depth++] = save_wv;
825           save_wv = prev_wv;
826           prev_wv = 0;
827           first_pane = 1;
828           i++;
829         }
830       else if (EQ (AREF (menu_items, i), Qlambda))
831         {
832           prev_wv = save_wv;
833           save_wv = submenu_stack[--submenu_depth];
834           first_pane = 0;
835           i++;
836         }
837       else if (EQ (AREF (menu_items, i), Qt)
838                && submenu_depth != 0)
839         i += MENU_ITEMS_PANE_LENGTH;
840       /* Ignore a nil in the item list.
841          It's meaningful only for dialog boxes.  */
842       else if (EQ (AREF (menu_items, i), Qquote))
843         i += 1;
844       else if (EQ (AREF (menu_items, i), Qt))
845         {
846           /* Create a new pane.  */
847           Lisp_Object pane_name, prefix;
848           const char *pane_string;
850           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
851           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
853 #ifndef HAVE_MULTILINGUAL_MENU
854           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
855             {
856               pane_name = ENCODE_MENU_STRING (pane_name);
857               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
858             }
859 #endif
860           pane_string = (NILP (pane_name)
861                          ? "" : SSDATA (pane_name));
862           /* If there is just one top-level pane, put all its items directly
863              under the top-level menu.  */
864           if (menu_items_n_panes == 1)
865             pane_string = "";
867           /* If the pane has a meaningful name,
868              make the pane a top-level menu item
869              with its items as a submenu beneath it.  */
870           if (!keymaps && strcmp (pane_string, ""))
871             {
872               wv = make_widget_value (pane_string, NULL, true, Qnil);
873               if (save_wv)
874                 save_wv->next = wv;
875               else
876                 first_wv->contents = wv;
877               if (keymaps && !NILP (prefix))
878                 wv->name++;
879               wv->button_type = BUTTON_TYPE_NONE;
880               save_wv = wv;
881               prev_wv = 0;
882             }
883           else if (first_pane)
884             {
885               save_wv = wv;
886               prev_wv = 0;
887             }
888           first_pane = 0;
889           i += MENU_ITEMS_PANE_LENGTH;
890         }
891       else
892         {
893           /* Create a new item within current pane.  */
894           Lisp_Object item_name, enable, descrip, def, type, selected, help;
895           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
896           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
897           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
898           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
899           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
900           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
901           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
903 #ifndef HAVE_MULTILINGUAL_MENU
904           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
905             {
906               item_name = ENCODE_MENU_STRING (item_name);
907               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
908             }
910           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
911             {
912               descrip = ENCODE_MENU_STRING (descrip);
913               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
914             }
915 #endif /* not HAVE_MULTILINGUAL_MENU */
917           wv = make_widget_value (SSDATA (item_name), NULL, !NILP (enable),
918                                   STRINGP (help) ? help : Qnil);
919           if (prev_wv)
920             prev_wv->next = wv;
921           else
922             save_wv->contents = wv;
923           if (!NILP (descrip))
924             wv->key = SSDATA (descrip);
925           /* If this item has a null value,
926              make the call_data null so that it won't display a box
927              when the mouse is on it.  */
928           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
930           if (NILP (type))
931             wv->button_type = BUTTON_TYPE_NONE;
932           else if (EQ (type, QCtoggle))
933             wv->button_type = BUTTON_TYPE_TOGGLE;
934           else if (EQ (type, QCradio))
935             wv->button_type = BUTTON_TYPE_RADIO;
936           else
937             emacs_abort ();
939           wv->selected = !NILP (selected);
941           prev_wv = wv;
943           i += MENU_ITEMS_ITEM_LENGTH;
944         }
945     }
946   }
947 #endif
949   if (!NILP (title))
950     {
951       widget_value *wv_title;
952       widget_value *wv_sep = make_widget_value ("--", NULL, false, Qnil);
954       /* Maybe replace this separator with a bitmap or owner-draw item
955          so that it looks better.  Having two separators looks odd.  */
956       wv_sep->next = first_wv->contents;
958 #ifndef HAVE_MULTILINGUAL_MENU
959       if (STRING_MULTIBYTE (title))
960         title = ENCODE_MENU_STRING (title);
961 #endif
962       wv_title = make_widget_value (SSDATA (title), NULL, false, Qnil);
963       wv_title->button_type = BUTTON_TYPE_NONE;
964       wv_title->next = wv_sep;
965       first_wv->contents = wv_title;
966     }
968   pmenu = [[EmacsMenu alloc] initWithTitle:
969                                [NSString stringWithUTF8String: SSDATA (title)]];
970   [pmenu fillWithWidgetValue: first_wv->contents];
971   free_menubar_widget_value_tree (first_wv);
972   unbind_to (specpdl_count, Qnil);
974   popup_activated_flag = 1;
975   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
976   popup_activated_flag = 0;
977   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
979   unblock_input ();
980   return tem;
984 /* ==========================================================================
986     Toolbar: externally-called functions
988    ========================================================================== */
990 void
991 free_frame_tool_bar (struct frame *f)
992 /* --------------------------------------------------------------------------
993     Under NS we just hide the toolbar until it might be needed again.
994    -------------------------------------------------------------------------- */
996   EmacsView *view = FRAME_NS_VIEW (f);
998   NSTRACE ("free_frame_tool_bar");
1000   block_input ();
1001   view->wait_for_tool_bar = NO;
1003   /* Note: This trigger an animation, which calls windowDidResize
1004      repeatedly. */
1005   f->output_data.ns->in_animation = 1;
1006   [[view toolbar] setVisible: NO];
1007   f->output_data.ns->in_animation = 0;
1009   unblock_input ();
1012 void
1013 update_frame_tool_bar (struct frame *f)
1014 /* --------------------------------------------------------------------------
1015     Update toolbar contents
1016    -------------------------------------------------------------------------- */
1018   int i, k = 0;
1019   EmacsView *view = FRAME_NS_VIEW (f);
1020   EmacsToolbar *toolbar = [view toolbar];
1021   int oldh;
1023   NSTRACE ("update_frame_tool_bar");
1025   if (view == nil || toolbar == nil) return;
1026   block_input ();
1028   oldh = FRAME_TOOLBAR_HEIGHT (f);
1030 #ifdef NS_IMPL_COCOA
1031   [toolbar clearActive];
1032 #else
1033   [toolbar clearAll];
1034 #endif
1036   /* update EmacsToolbar as in GtkUtils, build items list */
1037   for (i = 0; i < f->n_tool_bar_items; ++i)
1038     {
1039 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1040                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1042       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1043       int idx;
1044       ptrdiff_t img_id;
1045       struct image *img;
1046       Lisp_Object image;
1047       Lisp_Object helpObj;
1048       const char *helpText;
1050       /* Check if this is a separator.  */
1051       if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1052         {
1053           /* Skip separators.  Newer macOS don't show them, and on
1054              GNUstep they are wide as a button, thus overflowing the
1055              toolbar most of the time.  */
1056           continue;
1057         }
1059       /* If image is a vector, choose the image according to the
1060          button state.  */
1061       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1062       if (VECTORP (image))
1063         {
1064           /* NS toolbar auto-computes disabled and selected images */
1065           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1066           eassert (ASIZE (image) >= idx);
1067           image = AREF (image, idx);
1068         }
1069       else
1070         {
1071           idx = -1;
1072         }
1073       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1074       if (NILP (helpObj))
1075         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1076       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1078       /* Ignore invalid image specifications.  */
1079       if (!valid_image_p (image))
1080         {
1081           /* Don't log anything, GNUS makes invalid images all the time.  */
1082           continue;
1083         }
1085       img_id = lookup_image (f, image);
1086       img = IMAGE_FROM_ID (f, img_id);
1087       prepare_image_for_display (f, img);
1089       if (img->load_failed_p || img->pixmap == nil)
1090         {
1091           NSLog (@"Could not prepare toolbar image for display.");
1092           continue;
1093         }
1095       [toolbar addDisplayItemWithImage: img->pixmap
1096                                    idx: k++
1097                                    tag: i
1098                               helpText: helpText
1099                                enabled: enabled_p];
1100 #undef TOOLPROP
1101     }
1103   if (![toolbar isVisible])
1104     {
1105       f->output_data.ns->in_animation = 1;
1106       [toolbar setVisible: YES];
1107       f->output_data.ns->in_animation = 0;
1108     }
1110 #ifdef NS_IMPL_COCOA
1111   if ([toolbar changed])
1112     {
1113       /* inform app that toolbar has changed */
1114       NSDictionary *dict = [toolbar configurationDictionary];
1115       NSMutableDictionary *newDict = [dict mutableCopy];
1116       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1117       id key;
1118       while ((key = [keys nextObject]) != nil)
1119         {
1120           NSObject *val = [dict objectForKey: key];
1121           if ([val isKindOfClass: [NSArray class]])
1122             {
1123               [newDict setObject:
1124                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1125                           forKey: key];
1126               break;
1127             }
1128         }
1129       [toolbar setConfigurationFromDictionary: newDict];
1130       [newDict release];
1131     }
1132 #endif
1134   if (oldh != FRAME_TOOLBAR_HEIGHT (f))
1135     [view updateFrameSize:YES];
1136   if (view->wait_for_tool_bar && FRAME_TOOLBAR_HEIGHT (f) > 0)
1137     {
1138       view->wait_for_tool_bar = NO;
1139       [view setNeedsDisplay: YES];
1140     }
1142   unblock_input ();
1146 /* ==========================================================================
1148     Toolbar: class implementation
1150    ========================================================================== */
1152 @implementation EmacsToolbar
1154 - (instancetype)initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1156   NSTRACE ("[EmacsToolbar initForView: withIdentifier:]");
1158   self = [super initWithIdentifier: identifier];
1159   emacsView = view;
1160   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1161   [self setSizeMode: NSToolbarSizeModeSmall];
1162   [self setDelegate: self];
1163   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1164   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1165   prevIdentifiers = nil;
1166   prevEnablement = enablement = 0L;
1167   return self;
1170 - (void)dealloc
1172   NSTRACE ("[EmacsToolbar dealloc]");
1174   [prevIdentifiers release];
1175   [activeIdentifiers release];
1176   [identifierToItem release];
1177   [super dealloc];
1180 - (void) clearActive
1182   NSTRACE ("[EmacsToolbar clearActive]");
1184   [prevIdentifiers release];
1185   prevIdentifiers = [activeIdentifiers copy];
1186   [activeIdentifiers removeAllObjects];
1187   prevEnablement = enablement;
1188   enablement = 0L;
1191 - (void) clearAll
1193   NSTRACE ("[EmacsToolbar clearAll]");
1195   [self clearActive];
1196   while ([[self items] count] > 0)
1197     [self removeItemAtIndex: 0];
1200 - (BOOL) changed
1202   NSTRACE ("[EmacsToolbar changed]");
1204   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1205     enablement == prevEnablement ? NO : YES;
1208 - (void) addDisplayItemWithImage: (EmacsImage *)img
1209                              idx: (int)idx
1210                              tag: (int)tag
1211                         helpText: (const char *)help
1212                          enabled: (BOOL)enabled
1214   NSTRACE ("[EmacsToolbar addDisplayItemWithImage: ...]");
1216   /* 1) come up w/identifier */
1217   NSString *identifier
1218     = [NSString stringWithFormat: @"%lu", (unsigned long)[img hash]];
1219   [activeIdentifiers addObject: identifier];
1221   /* 2) create / reuse item */
1222   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1223   if (item == nil)
1224     {
1225       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1226                autorelease];
1227       [item setImage: img];
1228       [item setToolTip: [NSString stringWithUTF8String: help]];
1229       [item setTarget: emacsView];
1230       [item setAction: @selector (toolbarClicked:)];
1231       [identifierToItem setObject: item forKey: identifier];
1232     }
1234 #ifdef NS_IMPL_GNUSTEP
1235   [self insertItemWithItemIdentifier: identifier atIndex: idx];
1236 #endif
1238   [item setTag: tag];
1239   [item setEnabled: enabled];
1241   /* 3) update state */
1242   enablement = (enablement << 1) | (enabled == YES);
1245 /* This overrides super's implementation, which automatically sets
1246    all items to enabled state (for some reason). */
1247 - (void)validateVisibleItems
1249   NSTRACE ("[EmacsToolbar validateVisibleItems]");
1253 /* delegate methods */
1255 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1256       itemForItemIdentifier: (NSString *)itemIdentifier
1257   willBeInsertedIntoToolbar: (BOOL)flag
1259   NSTRACE ("[EmacsToolbar toolbar: ...]");
1261   /* look up NSToolbarItem by identifier and return... */
1262   return [identifierToItem objectForKey: itemIdentifier];
1265 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1267   NSTRACE ("[EmacsToolbar toolbarDefaultItemIdentifiers:]");
1269   /* return entire set.. */
1270   return activeIdentifiers;
1273 /* for configuration palette (not yet supported) */
1274 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1276   NSTRACE ("[EmacsToolbar toolbarAllowedItemIdentifiers:]");
1278   /* return entire set... */
1279   return activeIdentifiers;
1280   //return [identifierToItem allKeys];
1283 - (void)setVisible:(BOOL)shown
1285   NSTRACE ("[EmacsToolbar setVisible:%d]", shown);
1287   [super setVisible:shown];
1291 /* optional and unneeded */
1292 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1293 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1294 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1296 @end  /* EmacsToolbar */
1300 /* ==========================================================================
1302     Tooltip: class implementation
1304    ========================================================================== */
1306 /* Needed because NeXTstep does not provide enough control over tooltip
1307    display. */
1308 @implementation EmacsTooltip
1310 - (instancetype)init
1312   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1313                                             blue: 0.792 alpha: 0.95];
1314   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1315   NSFont *sfont = [font screenFont];
1316   int height = [sfont ascender] - [sfont descender];
1317 /*[font boundingRectForFont].size.height; */
1318   NSRect r = NSMakeRect (0, 0, 100, height+6);
1320   textField = [[NSTextField alloc] initWithFrame: r];
1321   [textField setFont: font];
1322   [textField setBackgroundColor: col];
1324   [textField setEditable: NO];
1325   [textField setSelectable: NO];
1326   [textField setBordered: NO];
1327   [textField setBezeled: NO];
1328   [textField setDrawsBackground: YES];
1330   win = [[NSWindow alloc]
1331             initWithContentRect: [textField frame]
1332                       styleMask: 0
1333                         backing: NSBackingStoreBuffered
1334                           defer: YES];
1335   [win setHasShadow: YES];
1336   [win setReleasedWhenClosed: NO];
1337   [win setDelegate: self];
1338   [[win contentView] addSubview: textField];
1339 /*  [win setBackgroundColor: col]; */
1340   [win setOpaque: NO];
1342   return self;
1345 - (void) dealloc
1347   [win close];
1348   [win release];
1349   [textField release];
1350   [super dealloc];
1353 - (void) setText: (char *)text
1355   NSString *str = [NSString stringWithUTF8String: text];
1356   NSRect r  = [textField frame];
1357   NSSize tooltipDims;
1359   [textField setStringValue: str];
1360   tooltipDims = [[textField cell] cellSize];
1362   r.size.width = tooltipDims.width;
1363   r.size.height = tooltipDims.height;
1364   [textField setFrame: r];
1367 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1369   NSRect wr = [win frame];
1371   wr.origin = NSMakePoint (x, y);
1372   wr.size = [textField frame].size;
1374   [win setFrame: wr display: YES];
1375   [win setLevel: NSPopUpMenuWindowLevel];
1376   [win orderFront: self];
1377   [win display];
1378   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1379                                          selector: @selector (hide)
1380                                          userInfo: nil repeats: NO];
1381   [timer retain];
1384 - (void) hide
1386   [win close];
1387   if (timer != nil)
1388     {
1389       if ([timer isValid])
1390         [timer invalidate];
1391       [timer release];
1392       timer = nil;
1393     }
1396 - (BOOL) isActive
1398   return timer != nil;
1401 - (NSRect) frame
1403   return [textField frame];
1406 @end  /* EmacsTooltip */
1410 /* ==========================================================================
1412     Popup Dialog: implementing functions
1414    ========================================================================== */
1416 static void
1417 pop_down_menu (void *arg)
1419   EmacsDialogPanel *panel = arg;
1421   if (popup_activated_flag)
1422     {
1423       block_input ();
1424       popup_activated_flag = 0;
1425       [panel close];
1426       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1427       unblock_input ();
1428     }
1432 Lisp_Object
1433 ns_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
1435   id dialog;
1436   Lisp_Object tem, title;
1437   NSPoint p;
1438   BOOL isQ;
1440   NSTRACE ("ns_popup_dialog");
1442   isQ = NILP (header);
1444   check_window_system (f);
1446   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1447   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1449   title = Fcar (contents);
1450   CHECK_STRING (title);
1452   if (NILP (Fcar (Fcdr (contents))))
1453     /* No buttons specified, add an "Ok" button so users can pop down
1454        the dialog.  */
1455     contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1457   block_input ();
1458   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1459                                            isQuestion: isQ];
1461   {
1462     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1464     record_unwind_protect_ptr (pop_down_menu, dialog);
1465     popup_activated_flag = 1;
1466     tem = [dialog runDialogAt: p];
1467     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1468   }
1470   unblock_input ();
1472   return tem;
1476 /* ==========================================================================
1478     Popup Dialog: class implementation
1480    ========================================================================== */
1482 @interface FlippedView : NSView
1485 @end
1487 @implementation FlippedView
1488 - (BOOL)isFlipped
1490   return YES;
1492 @end
1494 @implementation EmacsDialogPanel
1496 #define SPACER          8.0
1497 #define ICONSIZE        64.0
1498 #define TEXTHEIGHT      20.0
1499 #define MINCELLWIDTH    90.0
1501 - (instancetype)initWithContentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)aStyle
1502               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1504   NSSize spacing = {SPACER, SPACER};
1505   NSRect area;
1506   id cell;
1507   NSImageView *imgView;
1508   FlippedView *contentView;
1509   NSImage *img;
1511   dialog_return   = Qundefined;
1512   button_values   = NULL;
1513   area.origin.x   = 3*SPACER;
1514   area.origin.y   = 2*SPACER;
1515   area.size.width = ICONSIZE;
1516   area.size.height= ICONSIZE;
1517   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1518   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1519   imgView = [[NSImageView alloc] initWithFrame: area];
1520   [imgView setImage: img];
1521   [imgView setEditable: NO];
1522   [img autorelease];
1523   [imgView autorelease];
1525   aStyle = NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskUtilityWindow;
1526   flag = YES;
1527   rows = 0;
1528   cols = 1;
1529   [super initWithContentRect: contentRect styleMask: aStyle
1530                      backing: backingType defer: flag];
1531   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1532   [contentView autorelease];
1534   [self setContentView: contentView];
1536   [[self contentView] setAutoresizesSubviews: YES];
1538   [[self contentView] addSubview: imgView];
1539   [self setTitle: @""];
1541   area.origin.x   += ICONSIZE+2*SPACER;
1542 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1543   area.size.width = 400;
1544   area.size.height= TEXTHEIGHT;
1545   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1546   [[self contentView] addSubview: command];
1547   [command setStringValue: ns_app_name];
1548   [command setDrawsBackground: NO];
1549   [command setBezeled: NO];
1550   [command setSelectable: NO];
1551   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1553 /*  area.origin.x   = ICONSIZE+2*SPACER;
1554   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1555   area.size.width = 400;
1556   area.size.height= 2;
1557   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1558   [[self contentView] addSubview: tem];
1559   [tem setTitlePosition: NSNoTitle];
1560   [tem setAutoresizingMask: NSViewWidthSizable];*/
1562 /*  area.origin.x = ICONSIZE+2*SPACER; */
1563   area.origin.y += TEXTHEIGHT+SPACER;
1564   area.size.width = 400;
1565   area.size.height= TEXTHEIGHT;
1566   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1567   [[self contentView] addSubview: title];
1568   [title setDrawsBackground: NO];
1569   [title setBezeled: NO];
1570   [title setSelectable: NO];
1571   [title setFont: [NSFont systemFontOfSize: 11.0]];
1573   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1574   [cell setBordered: NO];
1575   [cell setEnabled: NO];
1576   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1577   [cell setBezelStyle: NSRoundedBezelStyle];
1579   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1580                                       mode: NSHighlightModeMatrix
1581                                  prototype: cell
1582                               numberOfRows: 0
1583                            numberOfColumns: 1];
1584   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1585                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1586   [matrix setIntercellSpacing: spacing];
1587   [matrix autorelease];
1589   [[self contentView] addSubview: matrix];
1590   [self setOneShot: YES];
1591   [self setReleasedWhenClosed: YES];
1592   [self setHidesOnDeactivate: YES];
1593   return self;
1597 - (BOOL)windowShouldClose: (id)sender
1599   window_closed = YES;
1600   [NSApp stop:self];
1601   return NO;
1604 - (void)dealloc
1606   xfree (button_values);
1607   [super dealloc];
1610 - (void)process_dialog: (Lisp_Object) list
1612   Lisp_Object item, lst = list;
1613   int row = 0;
1614   int buttons = 0, btnnr = 0;
1616   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1617     {
1618       item = XCAR (list);
1619       if (XTYPE (item) == Lisp_Cons)
1620         ++buttons;
1621     }
1623   if (buttons > 0)
1624     button_values = xmalloc (buttons * sizeof *button_values);
1626   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1627     {
1628       item = XCAR (list);
1629       if (XTYPE (item) == Lisp_String)
1630         {
1631           [self addString: SSDATA (item) row: row++];
1632         }
1633       else if (XTYPE (item) == Lisp_Cons)
1634         {
1635           button_values[btnnr] = XCDR (item);
1636           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1637           ++btnnr;
1638         }
1639       else if (NILP (item))
1640         {
1641           [self addSplit];
1642           row = 0;
1643         }
1644     }
1648 - (void)addButton: (char *)str value: (int)tag row: (int)row
1650   id cell;
1652   if (row >= rows)
1653     {
1654       [matrix addRow];
1655       rows++;
1656     }
1657   cell = [matrix cellAtRow: row column: cols-1];
1658   [cell setTarget: self];
1659   [cell setAction: @selector (clicked: )];
1660   [cell setTitle: [NSString stringWithUTF8String: str]];
1661   [cell setTag: tag];
1662   [cell setBordered: YES];
1663   [cell setEnabled: YES];
1667 - (void)addString: (char *)str row: (int)row
1669   id cell;
1671   if (row >= rows)
1672     {
1673       [matrix addRow];
1674       rows++;
1675     }
1676   cell = [matrix cellAtRow: row column: cols-1];
1677   [cell setTitle: [NSString stringWithUTF8String: str]];
1678   [cell setBordered: YES];
1679   [cell setEnabled: NO];
1683 - (void)addSplit
1685   [matrix addColumn];
1686   cols++;
1690 - (void)clicked: sender
1692   NSArray *sellist = nil;
1693   EMACS_INT seltag;
1695   sellist = [sender selectedCells];
1696   if ([sellist count] < 1)
1697     return;
1699   seltag = [[sellist objectAtIndex: 0] tag];
1700   dialog_return = button_values[seltag];
1701   [NSApp stop:self];
1705 - (instancetype)initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1707   Lisp_Object head;
1708   [super init];
1710   if (XTYPE (contents) == Lisp_Cons)
1711     {
1712       head = Fcar (contents);
1713       [self process_dialog: Fcdr (contents)];
1714     }
1715   else
1716     head = contents;
1718   if (XTYPE (head) == Lisp_String)
1719       [title setStringValue:
1720                  [NSString stringWithUTF8String: SSDATA (head)]];
1721   else if (isQ == YES)
1722       [title setStringValue: @"Question"];
1723   else
1724       [title setStringValue: @"Information"];
1726   {
1727     int i;
1728     NSRect r, s, t;
1730     if (cols == 1 && rows > 1)  /* Never told where to split */
1731       {
1732         [matrix addColumn];
1733         for (i = 0; i < rows/2; i++)
1734           {
1735             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1736                       atRow: i column: 1];
1737             [matrix removeRow: (rows+1)/2];
1738           }
1739       }
1741     [matrix sizeToFit];
1742     {
1743       NSSize csize = [matrix cellSize];
1744       if (csize.width < MINCELLWIDTH)
1745         {
1746           csize.width = MINCELLWIDTH;
1747           [matrix setCellSize: csize];
1748           [matrix sizeToCells];
1749         }
1750     }
1752     [title sizeToFit];
1753     [command sizeToFit];
1755     t = [matrix frame];
1756     r = [title frame];
1757     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1758       {
1759         t.origin.x   = r.origin.x;
1760         t.size.width = r.size.width;
1761       }
1762     r = [command frame];
1763     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1764       {
1765         t.origin.x   = r.origin.x;
1766         t.size.width = r.size.width;
1767       }
1769     r = [self frame];
1770     s = [(NSView *)[self contentView] frame];
1771     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1772     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1773     [self setFrame: r display: NO];
1774   }
1776   return self;
1781 - (void)timeout_handler: (NSTimer *)timedEntry
1783   NSEvent *nxev = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
1784                             location: NSMakePoint (0, 0)
1785                        modifierFlags: 0
1786                            timestamp: 0
1787                         windowNumber: [[NSApp mainWindow] windowNumber]
1788                              context: [NSApp context]
1789                              subtype: 0
1790                                data1: 0
1791                                data2: 0];
1793   timer_fired = YES;
1794   /* We use sto because stopModal/abortModal out of the main loop does not
1795      seem to work in 10.6.  But as we use stop we must send a real event so
1796      the stop is seen and acted upon.  */
1797   [NSApp stop:self];
1798   [NSApp postEvent: nxev atStart: NO];
1801 - (Lisp_Object)runDialogAt: (NSPoint)p
1803   Lisp_Object ret = Qundefined;
1805   while (popup_activated_flag)
1806     {
1807       NSTimer *tmo = nil;
1808       struct timespec next_time = timer_check ();
1810       if (timespec_valid_p (next_time))
1811         {
1812           double time = timespectod (next_time);
1813           tmo = [NSTimer timerWithTimeInterval: time
1814                                         target: self
1815                                       selector: @selector (timeout_handler:)
1816                                       userInfo: 0
1817                                        repeats: NO];
1818           [[NSRunLoop currentRunLoop] addTimer: tmo
1819                                        forMode: NSModalPanelRunLoopMode];
1820         }
1821       timer_fired = NO;
1822       dialog_return = Qundefined;
1823       [NSApp runModalForWindow: self];
1824       ret = dialog_return;
1825       if (! timer_fired)
1826         {
1827           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1828           break;
1829         }
1830     }
1832   if (EQ (ret, Qundefined) && window_closed)
1833     /* Make close button pressed equivalent to C-g.  */
1834     quit ();
1836   return ret;
1839 @end
1842 /* ==========================================================================
1844     Lisp definitions
1846    ========================================================================== */
1848 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1849        doc: /* Cause the NS menu to be re-calculated.  */)
1850      (void)
1852   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1853   return Qnil;
1857 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1858        doc: /* Return t if a menu or popup dialog is active.  */)
1859      (void)
1861   return popup_activated () ? Qt : Qnil;
1864 /* ==========================================================================
1866     Lisp interface declaration
1868    ========================================================================== */
1870 void
1871 syms_of_nsmenu (void)
1873 #ifndef NS_IMPL_COCOA
1874   /* Don't know how to keep track of this in Next/Open/GNUstep.  Always
1875      update menus there.  */
1876   trackingMenu = 1;
1877 #endif
1878   defsubr (&Sns_reset_menu);
1879   defsubr (&Smenu_or_popup_active_p);
1881   DEFSYM (Qdebug_on_next_call, "debug-on-next-call");