Document reserved keys
[emacs.git] / src / nsmenu.m
blob604adcf40b574e55f6851ee90f8a4e4c538539c7
1 /* NeXT/Open/GNUstep and macOS Cocoa menu and toolbar module.
2    Copyright (C) 2007-2018 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 mangles modifier strings longer than one character.  */
647       if (keyEquivModMask == 0)
648         {
649           title = [title stringByAppendingFormat: @" (%@)", keyEq];
650           item = [self addItemWithTitle: (NSString *)title
651                                  action: @selector (menuDown:)
652                           keyEquivalent: @""];
653         }
654       else
655         {
656 #endif
657           item = [self addItemWithTitle: (NSString *)title
658                                  action: @selector (menuDown:)
659                           keyEquivalent: keyEq];
660 #ifdef NS_IMPL_COCOA
661         }
662 #endif
663       [item setKeyEquivalentModifierMask: keyEquivModMask];
665       [item setEnabled: wv->enabled];
667       /* Draw radio buttons and tickboxes */
668       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
669                            wv->button_type == BUTTON_TYPE_RADIO))
670         [item setState: NSOnState];
671       else
672         [item setState: NSOffState];
674       [item setTag: (NSInteger)wv->call_data];
675     }
677   return item;
681 /* convenience */
682 -(void)clear
684   int n;
686   for (n = [self numberOfItems]-1; n >= 0; n--)
687     {
688       NSMenuItem *item = [self itemAtIndex: n];
689       NSString *title = [item title];
690       if ([ns_app_name isEqualToString: title]
691           && ![item isSeparatorItem])
692         continue;
693       [self removeItemAtIndex: n];
694     }
698 - (void)fillWithWidgetValue: (void *)wvptr
700   [self fillWithWidgetValue: wvptr frame: (struct frame *)nil];
703 - (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f
705   widget_value *wv = (widget_value *)wvptr;
707   /* clear existing contents */
708   [self clear];
710   /* add new contents */
711   for (; wv != NULL; wv = wv->next)
712     {
713       NSMenuItem *item = [self addItemWithWidgetValue: wv];
715       if (wv->contents)
716         {
717           EmacsMenu *submenu;
719           if (f)
720             submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f];
721           else
722             submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
724           [self setSubmenu: submenu forItem: item];
725           [submenu fillWithWidgetValue: wv->contents];
726           [submenu release];
727           [item setAction: (SEL)nil];
728         }
729     }
731 #ifdef NS_IMPL_GNUSTEP
732   if ([[self window] isVisible])
733     [self sizeToFit];
734 #endif
738 /* adds an empty submenu and returns it */
739 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
741   NSString *titleStr = [NSString stringWithUTF8String: title];
742   NSMenuItem *item = [self addItemWithTitle: titleStr
743                                      action: (SEL)nil /*@selector (menuDown:) */
744                               keyEquivalent: @""];
745   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
746   [self setSubmenu: submenu forItem: item];
747   [submenu release];
748   return submenu;
751 /* run a menu in popup mode */
752 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
753                  keymaps: (bool)keymaps
755   EmacsView *view = FRAME_NS_VIEW (f);
756   NSEvent *e, *event;
757   long retVal;
759 /*   p = [view convertPoint:p fromView: nil]; */
760   p.y = NSHeight ([view frame]) - p.y;
761   e = [[view window] currentEvent];
762    event = [NSEvent mouseEventWithType: NSEventTypeRightMouseDown
763                               location: p
764                          modifierFlags: 0
765                              timestamp: [e timestamp]
766                           windowNumber: [[view window] windowNumber]
767                                context: nil
768                            eventNumber: 0/*[e eventNumber] */
769                             clickCount: 1
770                               pressure: 0];
772   context_menu_value = -1;
773   [NSMenu popUpContextMenu: self withEvent: event forView: view];
774   retVal = context_menu_value;
775   context_menu_value = 0;
776   return retVal > 0
777       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
778       : Qnil;
781 @end  /* EmacsMenu */
785 /* ==========================================================================
787     Context Menu: implementing functions
789    ========================================================================== */
791 Lisp_Object
792 ns_menu_show (struct frame *f, int x, int y, int menuflags,
793               Lisp_Object title, const char **error)
795   EmacsMenu *pmenu;
796   NSPoint p;
797   Lisp_Object tem;
798   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
799   widget_value *wv, *first_wv = 0;
800   bool keymaps = (menuflags & MENU_KEYMAPS);
802   NSTRACE ("ns_menu_show");
804   block_input ();
806   p.x = x; p.y = y;
808   /* now parse stage 2 as in ns_update_menubar */
809   wv = make_widget_value ("contextmenu", NULL, true, Qnil);
810   wv->button_type = BUTTON_TYPE_NONE;
811   first_wv = wv;
813 #if 0
814   /* FIXME: a couple of one-line differences prevent reuse */
815   wv = digest_single_submenu (0, menu_items_used, 0);
816 #else
817   {
818   widget_value *save_wv = 0, *prev_wv = 0;
819   widget_value **submenu_stack
820     = alloca (menu_items_used * sizeof *submenu_stack);
821 /*   Lisp_Object *subprefix_stack
822        = alloca (menu_items_used * sizeof *subprefix_stack); */
823   int submenu_depth = 0;
824   int first_pane = 1;
825   int i;
827   /* Loop over all panes and items, filling in the tree.  */
828   i = 0;
829   while (i < menu_items_used)
830     {
831       if (EQ (AREF (menu_items, i), Qnil))
832         {
833           submenu_stack[submenu_depth++] = save_wv;
834           save_wv = prev_wv;
835           prev_wv = 0;
836           first_pane = 1;
837           i++;
838         }
839       else if (EQ (AREF (menu_items, i), Qlambda))
840         {
841           prev_wv = save_wv;
842           save_wv = submenu_stack[--submenu_depth];
843           first_pane = 0;
844           i++;
845         }
846       else if (EQ (AREF (menu_items, i), Qt)
847                && submenu_depth != 0)
848         i += MENU_ITEMS_PANE_LENGTH;
849       /* Ignore a nil in the item list.
850          It's meaningful only for dialog boxes.  */
851       else if (EQ (AREF (menu_items, i), Qquote))
852         i += 1;
853       else if (EQ (AREF (menu_items, i), Qt))
854         {
855           /* Create a new pane.  */
856           Lisp_Object pane_name, prefix;
857           const char *pane_string;
859           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
860           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
862 #ifndef HAVE_MULTILINGUAL_MENU
863           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
864             {
865               pane_name = ENCODE_MENU_STRING (pane_name);
866               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
867             }
868 #endif
869           pane_string = (NILP (pane_name)
870                          ? "" : SSDATA (pane_name));
871           /* If there is just one top-level pane, put all its items directly
872              under the top-level menu.  */
873           if (menu_items_n_panes == 1)
874             pane_string = "";
876           /* If the pane has a meaningful name,
877              make the pane a top-level menu item
878              with its items as a submenu beneath it.  */
879           if (!keymaps && strcmp (pane_string, ""))
880             {
881               wv = make_widget_value (pane_string, NULL, true, Qnil);
882               if (save_wv)
883                 save_wv->next = wv;
884               else
885                 first_wv->contents = wv;
886               if (keymaps && !NILP (prefix))
887                 wv->name++;
888               wv->button_type = BUTTON_TYPE_NONE;
889               save_wv = wv;
890               prev_wv = 0;
891             }
892           else if (first_pane)
893             {
894               save_wv = wv;
895               prev_wv = 0;
896             }
897           first_pane = 0;
898           i += MENU_ITEMS_PANE_LENGTH;
899         }
900       else
901         {
902           /* Create a new item within current pane.  */
903           Lisp_Object item_name, enable, descrip, def, type, selected, help;
904           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
905           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
906           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
907           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
908           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
909           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
910           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
912 #ifndef HAVE_MULTILINGUAL_MENU
913           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
914             {
915               item_name = ENCODE_MENU_STRING (item_name);
916               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
917             }
919           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
920             {
921               descrip = ENCODE_MENU_STRING (descrip);
922               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
923             }
924 #endif /* not HAVE_MULTILINGUAL_MENU */
926           wv = make_widget_value (SSDATA (item_name), NULL, !NILP (enable),
927                                   STRINGP (help) ? help : Qnil);
928           if (prev_wv)
929             prev_wv->next = wv;
930           else
931             save_wv->contents = wv;
932           if (!NILP (descrip))
933             wv->key = SSDATA (descrip);
934           /* If this item has a null value,
935              make the call_data null so that it won't display a box
936              when the mouse is on it.  */
937           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
939           if (NILP (type))
940             wv->button_type = BUTTON_TYPE_NONE;
941           else if (EQ (type, QCtoggle))
942             wv->button_type = BUTTON_TYPE_TOGGLE;
943           else if (EQ (type, QCradio))
944             wv->button_type = BUTTON_TYPE_RADIO;
945           else
946             emacs_abort ();
948           wv->selected = !NILP (selected);
950           prev_wv = wv;
952           i += MENU_ITEMS_ITEM_LENGTH;
953         }
954     }
955   }
956 #endif
958   if (!NILP (title))
959     {
960       widget_value *wv_title;
961       widget_value *wv_sep = make_widget_value ("--", NULL, false, Qnil);
963       /* Maybe replace this separator with a bitmap or owner-draw item
964          so that it looks better.  Having two separators looks odd.  */
965       wv_sep->next = first_wv->contents;
967 #ifndef HAVE_MULTILINGUAL_MENU
968       if (STRING_MULTIBYTE (title))
969         title = ENCODE_MENU_STRING (title);
970 #endif
971       wv_title = make_widget_value (SSDATA (title), NULL, false, Qnil);
972       wv_title->button_type = BUTTON_TYPE_NONE;
973       wv_title->next = wv_sep;
974       first_wv->contents = wv_title;
975     }
977   pmenu = [[EmacsMenu alloc] initWithTitle:
978                                [NSString stringWithUTF8String: SSDATA (title)]];
979   [pmenu fillWithWidgetValue: first_wv->contents];
980   free_menubar_widget_value_tree (first_wv);
981   unbind_to (specpdl_count, Qnil);
983   popup_activated_flag = 1;
984   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
985   popup_activated_flag = 0;
986   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
988   unblock_input ();
989   return tem;
993 /* ==========================================================================
995     Toolbar: externally-called functions
997    ========================================================================== */
999 void
1000 free_frame_tool_bar (struct frame *f)
1001 /* --------------------------------------------------------------------------
1002     Under NS we just hide the toolbar until it might be needed again.
1003    -------------------------------------------------------------------------- */
1005   EmacsView *view = FRAME_NS_VIEW (f);
1007   NSTRACE ("free_frame_tool_bar");
1009   block_input ();
1010   view->wait_for_tool_bar = NO;
1012   /* Note: This trigger an animation, which calls windowDidResize
1013      repeatedly. */
1014   f->output_data.ns->in_animation = 1;
1015   [[view toolbar] setVisible: NO];
1016   f->output_data.ns->in_animation = 0;
1018   unblock_input ();
1021 void
1022 update_frame_tool_bar (struct frame *f)
1023 /* --------------------------------------------------------------------------
1024     Update toolbar contents
1025    -------------------------------------------------------------------------- */
1027   int i, k = 0;
1028   EmacsView *view = FRAME_NS_VIEW (f);
1029   EmacsToolbar *toolbar = [view toolbar];
1030   int oldh;
1032   NSTRACE ("update_frame_tool_bar");
1034   if (view == nil || toolbar == nil) return;
1035   block_input ();
1037   oldh = FRAME_TOOLBAR_HEIGHT (f);
1039 #ifdef NS_IMPL_COCOA
1040   [toolbar clearActive];
1041 #else
1042   [toolbar clearAll];
1043 #endif
1045   /* update EmacsToolbar as in GtkUtils, build items list */
1046   for (i = 0; i < f->n_tool_bar_items; ++i)
1047     {
1048 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1049                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1051       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1052       int idx;
1053       ptrdiff_t img_id;
1054       struct image *img;
1055       Lisp_Object image;
1056       Lisp_Object helpObj;
1057       const char *helpText;
1059       /* Check if this is a separator.  */
1060       if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1061         {
1062           /* Skip separators.  Newer macOS don't show them, and on
1063              GNUstep they are wide as a button, thus overflowing the
1064              toolbar most of the time.  */
1065           continue;
1066         }
1068       /* If image is a vector, choose the image according to the
1069          button state.  */
1070       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1071       if (VECTORP (image))
1072         {
1073           /* NS toolbar auto-computes disabled and selected images */
1074           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1075           eassert (ASIZE (image) >= idx);
1076           image = AREF (image, idx);
1077         }
1078       else
1079         {
1080           idx = -1;
1081         }
1082       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1083       if (NILP (helpObj))
1084         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1085       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1087       /* Ignore invalid image specifications.  */
1088       if (!valid_image_p (image))
1089         {
1090           /* Don't log anything, GNUS makes invalid images all the time.  */
1091           continue;
1092         }
1094       img_id = lookup_image (f, image);
1095       img = IMAGE_FROM_ID (f, img_id);
1096       prepare_image_for_display (f, img);
1098       if (img->load_failed_p || img->pixmap == nil)
1099         {
1100           NSLog (@"Could not prepare toolbar image for display.");
1101           continue;
1102         }
1104       [toolbar addDisplayItemWithImage: img->pixmap
1105                                    idx: k++
1106                                    tag: i
1107                               helpText: helpText
1108                                enabled: enabled_p];
1109 #undef TOOLPROP
1110     }
1112   if (![toolbar isVisible])
1113     {
1114       f->output_data.ns->in_animation = 1;
1115       [toolbar setVisible: YES];
1116       f->output_data.ns->in_animation = 0;
1117     }
1119 #ifdef NS_IMPL_COCOA
1120   if ([toolbar changed])
1121     {
1122       /* inform app that toolbar has changed */
1123       NSDictionary *dict = [toolbar configurationDictionary];
1124       NSMutableDictionary *newDict = [dict mutableCopy];
1125       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1126       id key;
1127       while ((key = [keys nextObject]) != nil)
1128         {
1129           NSObject *val = [dict objectForKey: key];
1130           if ([val isKindOfClass: [NSArray class]])
1131             {
1132               [newDict setObject:
1133                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1134                           forKey: key];
1135               break;
1136             }
1137         }
1138       [toolbar setConfigurationFromDictionary: newDict];
1139       [newDict release];
1140     }
1141 #endif
1143   if (oldh != FRAME_TOOLBAR_HEIGHT (f))
1144     [view updateFrameSize:YES];
1145   if (view->wait_for_tool_bar && FRAME_TOOLBAR_HEIGHT (f) > 0)
1146     {
1147       view->wait_for_tool_bar = NO;
1148       [view setNeedsDisplay: YES];
1149     }
1151   unblock_input ();
1155 /* ==========================================================================
1157     Toolbar: class implementation
1159    ========================================================================== */
1161 @implementation EmacsToolbar
1163 - (instancetype)initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1165   NSTRACE ("[EmacsToolbar initForView: withIdentifier:]");
1167   self = [super initWithIdentifier: identifier];
1168   emacsView = view;
1169   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1170   [self setSizeMode: NSToolbarSizeModeSmall];
1171   [self setDelegate: self];
1172   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1173   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1174   prevIdentifiers = nil;
1175   prevEnablement = enablement = 0L;
1176   return self;
1179 - (void)dealloc
1181   NSTRACE ("[EmacsToolbar dealloc]");
1183   [prevIdentifiers release];
1184   [activeIdentifiers release];
1185   [identifierToItem release];
1186   [super dealloc];
1189 - (void) clearActive
1191   NSTRACE ("[EmacsToolbar clearActive]");
1193   [prevIdentifiers release];
1194   prevIdentifiers = [activeIdentifiers copy];
1195   [activeIdentifiers removeAllObjects];
1196   prevEnablement = enablement;
1197   enablement = 0L;
1200 - (void) clearAll
1202   NSTRACE ("[EmacsToolbar clearAll]");
1204   [self clearActive];
1205   while ([[self items] count] > 0)
1206     [self removeItemAtIndex: 0];
1209 - (BOOL) changed
1211   NSTRACE ("[EmacsToolbar changed]");
1213   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1214     enablement == prevEnablement ? NO : YES;
1217 - (void) addDisplayItemWithImage: (EmacsImage *)img
1218                              idx: (int)idx
1219                              tag: (int)tag
1220                         helpText: (const char *)help
1221                          enabled: (BOOL)enabled
1223   NSTRACE ("[EmacsToolbar addDisplayItemWithImage: ...]");
1225   /* 1) come up w/identifier */
1226   NSString *identifier
1227     = [NSString stringWithFormat: @"%lu", (unsigned long)[img hash]];
1228   [activeIdentifiers addObject: identifier];
1230   /* 2) create / reuse item */
1231   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1232   if (item == nil)
1233     {
1234       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1235                autorelease];
1236       [item setImage: img];
1237       [item setToolTip: [NSString stringWithUTF8String: help]];
1238       [item setTarget: emacsView];
1239       [item setAction: @selector (toolbarClicked:)];
1240       [identifierToItem setObject: item forKey: identifier];
1241     }
1243 #ifdef NS_IMPL_GNUSTEP
1244   [self insertItemWithItemIdentifier: identifier atIndex: idx];
1245 #endif
1247   [item setTag: tag];
1248   [item setEnabled: enabled];
1250   /* 3) update state */
1251   enablement = (enablement << 1) | (enabled == YES);
1254 /* This overrides super's implementation, which automatically sets
1255    all items to enabled state (for some reason). */
1256 - (void)validateVisibleItems
1258   NSTRACE ("[EmacsToolbar validateVisibleItems]");
1262 /* delegate methods */
1264 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1265       itemForItemIdentifier: (NSString *)itemIdentifier
1266   willBeInsertedIntoToolbar: (BOOL)flag
1268   NSTRACE ("[EmacsToolbar toolbar: ...]");
1270   /* look up NSToolbarItem by identifier and return... */
1271   return [identifierToItem objectForKey: itemIdentifier];
1274 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1276   NSTRACE ("[EmacsToolbar toolbarDefaultItemIdentifiers:]");
1278   /* return entire set.. */
1279   return activeIdentifiers;
1282 /* for configuration palette (not yet supported) */
1283 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1285   NSTRACE ("[EmacsToolbar toolbarAllowedItemIdentifiers:]");
1287   /* return entire set... */
1288   return activeIdentifiers;
1289   //return [identifierToItem allKeys];
1292 - (void)setVisible:(BOOL)shown
1294   NSTRACE ("[EmacsToolbar setVisible:%d]", shown);
1296   [super setVisible:shown];
1300 /* optional and unneeded */
1301 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1302 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1303 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1305 @end  /* EmacsToolbar */
1309 /* ==========================================================================
1311     Tooltip: class implementation
1313    ========================================================================== */
1315 /* Needed because NeXTstep does not provide enough control over tooltip
1316    display. */
1317 @implementation EmacsTooltip
1319 - (instancetype)init
1321   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1322                                             blue: 0.792 alpha: 0.95];
1323   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1324   NSFont *sfont = [font screenFont];
1325   int height = [sfont ascender] - [sfont descender];
1326 /*[font boundingRectForFont].size.height; */
1327   NSRect r = NSMakeRect (0, 0, 100, height+6);
1329   textField = [[NSTextField alloc] initWithFrame: r];
1330   [textField setFont: font];
1331   [textField setBackgroundColor: col];
1333   [textField setEditable: NO];
1334   [textField setSelectable: NO];
1335   [textField setBordered: NO];
1336   [textField setBezeled: NO];
1337   [textField setDrawsBackground: YES];
1339   win = [[NSWindow alloc]
1340             initWithContentRect: [textField frame]
1341                       styleMask: 0
1342                         backing: NSBackingStoreBuffered
1343                           defer: YES];
1344   [win setHasShadow: YES];
1345   [win setReleasedWhenClosed: NO];
1346   [win setDelegate: self];
1347   [[win contentView] addSubview: textField];
1348 /*  [win setBackgroundColor: col]; */
1349   [win setOpaque: NO];
1351   return self;
1354 - (void) dealloc
1356   [win close];
1357   [win release];
1358   [textField release];
1359   [super dealloc];
1362 - (void) setText: (char *)text
1364   NSString *str = [NSString stringWithUTF8String: text];
1365   NSRect r  = [textField frame];
1366   NSSize tooltipDims;
1368   [textField setStringValue: str];
1369   tooltipDims = [[textField cell] cellSize];
1371   r.size.width = tooltipDims.width;
1372   r.size.height = tooltipDims.height;
1373   [textField setFrame: r];
1376 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1378   NSRect wr = [win frame];
1380   wr.origin = NSMakePoint (x, y);
1381   wr.size = [textField frame].size;
1383   [win setFrame: wr display: YES];
1384   [win setLevel: NSPopUpMenuWindowLevel];
1385   [win orderFront: self];
1386   [win display];
1387   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1388                                          selector: @selector (hide)
1389                                          userInfo: nil repeats: NO];
1390   [timer retain];
1393 - (void) hide
1395   [win close];
1396   if (timer != nil)
1397     {
1398       if ([timer isValid])
1399         [timer invalidate];
1400       [timer release];
1401       timer = nil;
1402     }
1405 - (BOOL) isActive
1407   return timer != nil;
1410 - (NSRect) frame
1412   return [textField frame];
1415 @end  /* EmacsTooltip */
1419 /* ==========================================================================
1421     Popup Dialog: implementing functions
1423    ========================================================================== */
1425 static void
1426 pop_down_menu (void *arg)
1428   EmacsDialogPanel *panel = arg;
1430   if (popup_activated_flag)
1431     {
1432       block_input ();
1433       popup_activated_flag = 0;
1434       [panel close];
1435       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1436       unblock_input ();
1437     }
1441 Lisp_Object
1442 ns_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
1444   id dialog;
1445   Lisp_Object tem, title;
1446   NSPoint p;
1447   BOOL isQ;
1449   NSTRACE ("ns_popup_dialog");
1451   isQ = NILP (header);
1453   check_window_system (f);
1455   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1456   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1458   title = Fcar (contents);
1459   CHECK_STRING (title);
1461   if (NILP (Fcar (Fcdr (contents))))
1462     /* No buttons specified, add an "Ok" button so users can pop down
1463        the dialog.  */
1464     contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1466   block_input ();
1467   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1468                                            isQuestion: isQ];
1470   {
1471     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1473     record_unwind_protect_ptr (pop_down_menu, dialog);
1474     popup_activated_flag = 1;
1475     tem = [dialog runDialogAt: p];
1476     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1477   }
1479   unblock_input ();
1481   return tem;
1485 /* ==========================================================================
1487     Popup Dialog: class implementation
1489    ========================================================================== */
1491 @interface FlippedView : NSView
1494 @end
1496 @implementation FlippedView
1497 - (BOOL)isFlipped
1499   return YES;
1501 @end
1503 @implementation EmacsDialogPanel
1505 #define SPACER          8.0
1506 #define ICONSIZE        64.0
1507 #define TEXTHEIGHT      20.0
1508 #define MINCELLWIDTH    90.0
1510 - (instancetype)initWithContentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)aStyle
1511               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1513   NSSize spacing = {SPACER, SPACER};
1514   NSRect area;
1515   id cell;
1516   NSImageView *imgView;
1517   FlippedView *contentView;
1518   NSImage *img;
1520   dialog_return   = Qundefined;
1521   button_values   = NULL;
1522   area.origin.x   = 3*SPACER;
1523   area.origin.y   = 2*SPACER;
1524   area.size.width = ICONSIZE;
1525   area.size.height= ICONSIZE;
1526   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1527   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1528   imgView = [[NSImageView alloc] initWithFrame: area];
1529   [imgView setImage: img];
1530   [imgView setEditable: NO];
1531   [img autorelease];
1532   [imgView autorelease];
1534   aStyle = NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskUtilityWindow;
1535   flag = YES;
1536   rows = 0;
1537   cols = 1;
1538   [super initWithContentRect: contentRect styleMask: aStyle
1539                      backing: backingType defer: flag];
1540   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1541   [contentView autorelease];
1543   [self setContentView: contentView];
1545   [[self contentView] setAutoresizesSubviews: YES];
1547   [[self contentView] addSubview: imgView];
1548   [self setTitle: @""];
1550   area.origin.x   += ICONSIZE+2*SPACER;
1551 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1552   area.size.width = 400;
1553   area.size.height= TEXTHEIGHT;
1554   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1555   [[self contentView] addSubview: command];
1556   [command setStringValue: ns_app_name];
1557   [command setDrawsBackground: NO];
1558   [command setBezeled: NO];
1559   [command setSelectable: NO];
1560   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1562 /*  area.origin.x   = ICONSIZE+2*SPACER;
1563   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1564   area.size.width = 400;
1565   area.size.height= 2;
1566   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1567   [[self contentView] addSubview: tem];
1568   [tem setTitlePosition: NSNoTitle];
1569   [tem setAutoresizingMask: NSViewWidthSizable];*/
1571 /*  area.origin.x = ICONSIZE+2*SPACER; */
1572   area.origin.y += TEXTHEIGHT+SPACER;
1573   area.size.width = 400;
1574   area.size.height= TEXTHEIGHT;
1575   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1576   [[self contentView] addSubview: title];
1577   [title setDrawsBackground: NO];
1578   [title setBezeled: NO];
1579   [title setSelectable: NO];
1580   [title setFont: [NSFont systemFontOfSize: 11.0]];
1582   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1583   [cell setBordered: NO];
1584   [cell setEnabled: NO];
1585   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1586   [cell setBezelStyle: NSRoundedBezelStyle];
1588   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1589                                       mode: NSHighlightModeMatrix
1590                                  prototype: cell
1591                               numberOfRows: 0
1592                            numberOfColumns: 1];
1593   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1594                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1595   [matrix setIntercellSpacing: spacing];
1596   [matrix autorelease];
1598   [[self contentView] addSubview: matrix];
1599   [self setOneShot: YES];
1600   [self setReleasedWhenClosed: YES];
1601   [self setHidesOnDeactivate: YES];
1602   return self;
1606 - (BOOL)windowShouldClose: (id)sender
1608   window_closed = YES;
1609   [NSApp stop:self];
1610   return NO;
1613 - (void)dealloc
1615   xfree (button_values);
1616   [super dealloc];
1619 - (void)process_dialog: (Lisp_Object) list
1621   Lisp_Object item, lst = list;
1622   int row = 0;
1623   int buttons = 0, btnnr = 0;
1625   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1626     {
1627       item = XCAR (list);
1628       if (XTYPE (item) == Lisp_Cons)
1629         ++buttons;
1630     }
1632   if (buttons > 0)
1633     button_values = xmalloc (buttons * sizeof *button_values);
1635   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1636     {
1637       item = XCAR (list);
1638       if (XTYPE (item) == Lisp_String)
1639         {
1640           [self addString: SSDATA (item) row: row++];
1641         }
1642       else if (XTYPE (item) == Lisp_Cons)
1643         {
1644           button_values[btnnr] = XCDR (item);
1645           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1646           ++btnnr;
1647         }
1648       else if (NILP (item))
1649         {
1650           [self addSplit];
1651           row = 0;
1652         }
1653     }
1657 - (void)addButton: (char *)str value: (int)tag row: (int)row
1659   id cell;
1661   if (row >= rows)
1662     {
1663       [matrix addRow];
1664       rows++;
1665     }
1666   cell = [matrix cellAtRow: row column: cols-1];
1667   [cell setTarget: self];
1668   [cell setAction: @selector (clicked: )];
1669   [cell setTitle: [NSString stringWithUTF8String: str]];
1670   [cell setTag: tag];
1671   [cell setBordered: YES];
1672   [cell setEnabled: YES];
1676 - (void)addString: (char *)str row: (int)row
1678   id cell;
1680   if (row >= rows)
1681     {
1682       [matrix addRow];
1683       rows++;
1684     }
1685   cell = [matrix cellAtRow: row column: cols-1];
1686   [cell setTitle: [NSString stringWithUTF8String: str]];
1687   [cell setBordered: YES];
1688   [cell setEnabled: NO];
1692 - (void)addSplit
1694   [matrix addColumn];
1695   cols++;
1699 - (void)clicked: sender
1701   NSArray *sellist = nil;
1702   EMACS_INT seltag;
1704   sellist = [sender selectedCells];
1705   if ([sellist count] < 1)
1706     return;
1708   seltag = [[sellist objectAtIndex: 0] tag];
1709   dialog_return = button_values[seltag];
1710   [NSApp stop:self];
1714 - (instancetype)initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1716   Lisp_Object head;
1717   [super init];
1719   if (XTYPE (contents) == Lisp_Cons)
1720     {
1721       head = Fcar (contents);
1722       [self process_dialog: Fcdr (contents)];
1723     }
1724   else
1725     head = contents;
1727   if (XTYPE (head) == Lisp_String)
1728       [title setStringValue:
1729                  [NSString stringWithUTF8String: SSDATA (head)]];
1730   else if (isQ == YES)
1731       [title setStringValue: @"Question"];
1732   else
1733       [title setStringValue: @"Information"];
1735   {
1736     int i;
1737     NSRect r, s, t;
1739     if (cols == 1 && rows > 1)  /* Never told where to split */
1740       {
1741         [matrix addColumn];
1742         for (i = 0; i < rows/2; i++)
1743           {
1744             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1745                       atRow: i column: 1];
1746             [matrix removeRow: (rows+1)/2];
1747           }
1748       }
1750     [matrix sizeToFit];
1751     {
1752       NSSize csize = [matrix cellSize];
1753       if (csize.width < MINCELLWIDTH)
1754         {
1755           csize.width = MINCELLWIDTH;
1756           [matrix setCellSize: csize];
1757           [matrix sizeToCells];
1758         }
1759     }
1761     [title sizeToFit];
1762     [command sizeToFit];
1764     t = [matrix frame];
1765     r = [title frame];
1766     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1767       {
1768         t.origin.x   = r.origin.x;
1769         t.size.width = r.size.width;
1770       }
1771     r = [command frame];
1772     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1773       {
1774         t.origin.x   = r.origin.x;
1775         t.size.width = r.size.width;
1776       }
1778     r = [self frame];
1779     s = [(NSView *)[self contentView] frame];
1780     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1781     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1782     [self setFrame: r display: NO];
1783   }
1785   return self;
1790 - (void)timeout_handler: (NSTimer *)timedEntry
1792   NSEvent *nxev = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
1793                             location: NSMakePoint (0, 0)
1794                        modifierFlags: 0
1795                            timestamp: 0
1796                         windowNumber: [[NSApp mainWindow] windowNumber]
1797                              context: [NSApp context]
1798                              subtype: 0
1799                                data1: 0
1800                                data2: 0];
1802   timer_fired = YES;
1803   /* We use sto because stopModal/abortModal out of the main loop does not
1804      seem to work in 10.6.  But as we use stop we must send a real event so
1805      the stop is seen and acted upon.  */
1806   [NSApp stop:self];
1807   [NSApp postEvent: nxev atStart: NO];
1810 - (Lisp_Object)runDialogAt: (NSPoint)p
1812   Lisp_Object ret = Qundefined;
1814   while (popup_activated_flag)
1815     {
1816       NSTimer *tmo = nil;
1817       struct timespec next_time = timer_check ();
1819       if (timespec_valid_p (next_time))
1820         {
1821           double time = timespectod (next_time);
1822           tmo = [NSTimer timerWithTimeInterval: time
1823                                         target: self
1824                                       selector: @selector (timeout_handler:)
1825                                       userInfo: 0
1826                                        repeats: NO];
1827           [[NSRunLoop currentRunLoop] addTimer: tmo
1828                                        forMode: NSModalPanelRunLoopMode];
1829         }
1830       timer_fired = NO;
1831       dialog_return = Qundefined;
1832       [NSApp runModalForWindow: self];
1833       ret = dialog_return;
1834       if (! timer_fired)
1835         {
1836           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1837           break;
1838         }
1839     }
1841   if (EQ (ret, Qundefined) && window_closed)
1842     /* Make close button pressed equivalent to C-g.  */
1843     quit ();
1845   return ret;
1848 @end
1851 /* ==========================================================================
1853     Lisp definitions
1855    ========================================================================== */
1857 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1858        doc: /* Cause the NS menu to be re-calculated.  */)
1859      (void)
1861   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1862   return Qnil;
1866 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1867        doc: /* Return t if a menu or popup dialog is active.  */)
1868      (void)
1870   return popup_activated () ? Qt : Qnil;
1873 /* ==========================================================================
1875     Lisp interface declaration
1877    ========================================================================== */
1879 void
1880 syms_of_nsmenu (void)
1882 #ifndef NS_IMPL_COCOA
1883   /* Don't know how to keep track of this in Next/Open/GNUstep.  Always
1884      update menus there.  */
1885   trackingMenu = 1;
1886 #endif
1887   defsubr (&Sns_reset_menu);
1888   defsubr (&Smenu_or_popup_active_p);
1890   DEFSYM (Qdebug_on_next_call, "debug-on-next-call");