; Improve commentary of Info-default-directory-list
[emacs.git] / src / nsmenu.m
blob37a1a62d6d3a0b77a0bea57751f5fd401da61af5
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 <http://www.gnu.org/licenses/>.  */
20 By Adrian Robert, based on code from original nsmenu.m (Carl Edman,
21 Christian Limpach, Scott Bender, Christophe de Dinechin) and code in the
22 Carbon version by Yamamoto Mitsuharu. */
24 /* This should be the first include, as it may set up #defines affecting
25    interpretation of even the system includes. */
26 #include <config.h>
28 #include "lisp.h"
29 #include "window.h"
30 #include "character.h"
31 #include "buffer.h"
32 #include "keymap.h"
33 #include "coding.h"
34 #include "commands.h"
35 #include "blockinput.h"
36 #include "nsterm.h"
37 #include "termhooks.h"
38 #include "keyboard.h"
39 #include "menu.h"
41 #define NSMENUPROFILE 0
43 #if NSMENUPROFILE
44 #include <sys/timeb.h>
45 #include <sys/types.h>
46 #endif
49 #if 0
50 /* Include lisp -> C common menu parsing code */
51 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
52 #include "nsmenu_common.c"
53 #endif
55 extern long context_menu_value;
56 EmacsMenu *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_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
536   // On 10.6 we get repeated calls, only the one for NSSystemDefined is "real".
537   if ([[NSApp currentEvent] type] != NSSystemDefined) return;
538 #endif
540   /* When dragging from one menu to another, we get willOpen followed by didClose,
541      i.e. trackingMenu == 3 in willOpen and then 2 after didClose.
542      We have updated all menus, so avoid doing it when trackingMenu == 3.  */
543   if (trackingMenu == 2)
544     ns_check_menu_open (menu);
547 - (void)menuDidClose:(NSMenu *)menu
549   --trackingMenu;
552 #endif /* NS_IMPL_COCOA */
554 /* delegate method called when a submenu is being opened: run a 'deep' call
555    to set_frame_menubar */
556 - (void)menuNeedsUpdate: (NSMenu *)menu
558   if (!FRAME_LIVE_P (frame))
559     return;
561   /* Cocoa/Carbon will request update on every keystroke
562      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
563      since key equivalents are handled through emacs.
564      On Leopard, even keystroke events generate SystemDefined event.
565      Third-party applications that enhance mouse / trackpad
566      interaction, or also VNC/Remote Desktop will send events
567      of type AppDefined rather than SysDefined.
568      Menus will fail to show up if they haven't been initialized.
569      AppDefined events may lack timing data.
571      Thus, we rely on the didBeginTrackingNotification notification
572      as above to indicate the need for updates.
573      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
574      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
575   */
576   if (trackingMenu == 0)
577     return;
578 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
579 #ifdef NS_IMPL_GNUSTEP
580   /* Don't know how to do this for anything other than Mac OS X 10.5 and later.
581      This is wrong, as it might run Lisp code in the event loop.  */
582   ns_update_menubar (frame, true, self);
583 #endif
587 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
589   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
590       && FRAME_NS_VIEW (SELECTED_FRAME ()))
591     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
592   return YES;
596 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
597    into an accelerator string.  We are only able to display a single character
598    for an accelerator, together with an optional modifier combination.  (Under
599    Carbon more control was possible, but in Cocoa multi-char strings passed to
600    NSMenuItem get ignored.  For now we try to display a super-single letter
601    combo, and return the others as strings to be appended to the item title.
602    (This is signaled by setting keyEquivModMask to 0 for now.) */
603 -(NSString *)parseKeyEquiv: (const char *)key
605   const char *tpos = key;
606   keyEquivModMask = NSEventModifierFlagCommand;
608   if (!key || !strlen (key))
609     return @"";
611   while (*tpos == ' ' || *tpos == '(')
612     tpos++;
613   if ((*tpos == 's') && (*(tpos+1) == '-'))
614     {
615       return [NSString stringWithFormat: @"%c", tpos[2]];
616     }
617   keyEquivModMask = 0; /* signal */
618   return [NSString stringWithUTF8String: tpos];
622 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
624   NSMenuItem *item;
625   widget_value *wv = (widget_value *)wvptr;
627   if (menu_separator_name_p (wv->name))
628     {
629       item = [NSMenuItem separatorItem];
630       [self addItem: item];
631     }
632   else
633     {
634       NSString *title, *keyEq;
635       title = [NSString stringWithUTF8String: wv->name];
636       if (title == nil)
637         title = @"< ? >";  /* (get out in the open so we know about it) */
639       keyEq = [self parseKeyEquiv: wv->key];
640 #ifdef NS_IMPL_COCOA
641       /* macOS just ignores modifier strings longer than one character */
642       if (keyEquivModMask == 0)
643         title = [title stringByAppendingFormat: @" (%@)", keyEq];
644 #endif
646       item = [self addItemWithTitle: (NSString *)title
647                              action: @selector (menuDown:)
648                       keyEquivalent: keyEq];
649       [item setKeyEquivalentModifierMask: keyEquivModMask];
651       [item setEnabled: wv->enabled];
653       /* Draw radio buttons and tickboxes */
654       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
655                            wv->button_type == BUTTON_TYPE_RADIO))
656         [item setState: NSOnState];
657       else
658         [item setState: NSOffState];
660       [item setTag: (NSInteger)wv->call_data];
661     }
663   return item;
667 /* convenience */
668 -(void)clear
670   int n;
672   for (n = [self numberOfItems]-1; n >= 0; n--)
673     {
674       NSMenuItem *item = [self itemAtIndex: n];
675       NSString *title = [item title];
676       if ([ns_app_name isEqualToString: title]
677           && ![item isSeparatorItem])
678         continue;
679       [self removeItemAtIndex: n];
680     }
684 - (void)fillWithWidgetValue: (void *)wvptr
686   [self fillWithWidgetValue: wvptr frame: (struct frame *)nil];
689 - (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f
691   widget_value *wv = (widget_value *)wvptr;
693   /* clear existing contents */
694   [self clear];
696   /* add new contents */
697   for (; wv != NULL; wv = wv->next)
698     {
699       NSMenuItem *item = [self addItemWithWidgetValue: wv];
701       if (wv->contents)
702         {
703           EmacsMenu *submenu;
705           if (f)
706             submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f];
707           else
708             submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
710           [self setSubmenu: submenu forItem: item];
711           [submenu fillWithWidgetValue: wv->contents];
712           [submenu release];
713           [item setAction: (SEL)nil];
714         }
715     }
717 #ifdef NS_IMPL_GNUSTEP
718   if ([[self window] isVisible])
719     [self sizeToFit];
720 #endif
724 /* adds an empty submenu and returns it */
725 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
727   NSString *titleStr = [NSString stringWithUTF8String: title];
728   NSMenuItem *item = [self addItemWithTitle: titleStr
729                                      action: (SEL)nil /*@selector (menuDown:) */
730                               keyEquivalent: @""];
731   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
732   [self setSubmenu: submenu forItem: item];
733   [submenu release];
734   return submenu;
737 /* run a menu in popup mode */
738 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
739                  keymaps: (bool)keymaps
741   EmacsView *view = FRAME_NS_VIEW (f);
742   NSEvent *e, *event;
743   long retVal;
745 /*   p = [view convertPoint:p fromView: nil]; */
746   p.y = NSHeight ([view frame]) - p.y;
747   e = [[view window] currentEvent];
748    event = [NSEvent mouseEventWithType: NSEventTypeRightMouseDown
749                               location: p
750                          modifierFlags: 0
751                              timestamp: [e timestamp]
752                           windowNumber: [[view window] windowNumber]
753                                context: nil
754                            eventNumber: 0/*[e eventNumber] */
755                             clickCount: 1
756                               pressure: 0];
758   context_menu_value = -1;
759   [NSMenu popUpContextMenu: self withEvent: event forView: view];
760   retVal = context_menu_value;
761   context_menu_value = 0;
762   return retVal > 0
763       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
764       : Qnil;
767 @end  /* EmacsMenu */
771 /* ==========================================================================
773     Context Menu: implementing functions
775    ========================================================================== */
777 Lisp_Object
778 ns_menu_show (struct frame *f, int x, int y, int menuflags,
779               Lisp_Object title, const char **error)
781   EmacsMenu *pmenu;
782   NSPoint p;
783   Lisp_Object tem;
784   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
785   widget_value *wv, *first_wv = 0;
786   bool keymaps = (menuflags & MENU_KEYMAPS);
788   NSTRACE ("ns_menu_show");
790   block_input ();
792   p.x = x; p.y = y;
794   /* now parse stage 2 as in ns_update_menubar */
795   wv = make_widget_value ("contextmenu", NULL, true, Qnil);
796   wv->button_type = BUTTON_TYPE_NONE;
797   first_wv = wv;
799 #if 0
800   /* FIXME: a couple of one-line differences prevent reuse */
801   wv = digest_single_submenu (0, menu_items_used, 0);
802 #else
803   {
804   widget_value *save_wv = 0, *prev_wv = 0;
805   widget_value **submenu_stack
806     = alloca (menu_items_used * sizeof *submenu_stack);
807 /*   Lisp_Object *subprefix_stack
808        = alloca (menu_items_used * sizeof *subprefix_stack); */
809   int submenu_depth = 0;
810   int first_pane = 1;
811   int i;
813   /* Loop over all panes and items, filling in the tree.  */
814   i = 0;
815   while (i < menu_items_used)
816     {
817       if (EQ (AREF (menu_items, i), Qnil))
818         {
819           submenu_stack[submenu_depth++] = save_wv;
820           save_wv = prev_wv;
821           prev_wv = 0;
822           first_pane = 1;
823           i++;
824         }
825       else if (EQ (AREF (menu_items, i), Qlambda))
826         {
827           prev_wv = save_wv;
828           save_wv = submenu_stack[--submenu_depth];
829           first_pane = 0;
830           i++;
831         }
832       else if (EQ (AREF (menu_items, i), Qt)
833                && submenu_depth != 0)
834         i += MENU_ITEMS_PANE_LENGTH;
835       /* Ignore a nil in the item list.
836          It's meaningful only for dialog boxes.  */
837       else if (EQ (AREF (menu_items, i), Qquote))
838         i += 1;
839       else if (EQ (AREF (menu_items, i), Qt))
840         {
841           /* Create a new pane.  */
842           Lisp_Object pane_name, prefix;
843           const char *pane_string;
845           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
846           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
848 #ifndef HAVE_MULTILINGUAL_MENU
849           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
850             {
851               pane_name = ENCODE_MENU_STRING (pane_name);
852               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
853             }
854 #endif
855           pane_string = (NILP (pane_name)
856                          ? "" : SSDATA (pane_name));
857           /* If there is just one top-level pane, put all its items directly
858              under the top-level menu.  */
859           if (menu_items_n_panes == 1)
860             pane_string = "";
862           /* If the pane has a meaningful name,
863              make the pane a top-level menu item
864              with its items as a submenu beneath it.  */
865           if (!keymaps && strcmp (pane_string, ""))
866             {
867               wv = make_widget_value (pane_string, NULL, true, Qnil);
868               if (save_wv)
869                 save_wv->next = wv;
870               else
871                 first_wv->contents = wv;
872               if (keymaps && !NILP (prefix))
873                 wv->name++;
874               wv->button_type = BUTTON_TYPE_NONE;
875               save_wv = wv;
876               prev_wv = 0;
877             }
878           else if (first_pane)
879             {
880               save_wv = wv;
881               prev_wv = 0;
882             }
883           first_pane = 0;
884           i += MENU_ITEMS_PANE_LENGTH;
885         }
886       else
887         {
888           /* Create a new item within current pane.  */
889           Lisp_Object item_name, enable, descrip, def, type, selected, help;
890           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
891           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
892           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
893           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
894           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
895           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
896           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
898 #ifndef HAVE_MULTILINGUAL_MENU
899           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
900             {
901               item_name = ENCODE_MENU_STRING (item_name);
902               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
903             }
905           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
906             {
907               descrip = ENCODE_MENU_STRING (descrip);
908               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
909             }
910 #endif /* not HAVE_MULTILINGUAL_MENU */
912           wv = make_widget_value (SSDATA (item_name), NULL, !NILP (enable),
913                                   STRINGP (help) ? help : Qnil);
914           if (prev_wv)
915             prev_wv->next = wv;
916           else
917             save_wv->contents = wv;
918           if (!NILP (descrip))
919             wv->key = SSDATA (descrip);
920           /* If this item has a null value,
921              make the call_data null so that it won't display a box
922              when the mouse is on it.  */
923           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
925           if (NILP (type))
926             wv->button_type = BUTTON_TYPE_NONE;
927           else if (EQ (type, QCtoggle))
928             wv->button_type = BUTTON_TYPE_TOGGLE;
929           else if (EQ (type, QCradio))
930             wv->button_type = BUTTON_TYPE_RADIO;
931           else
932             emacs_abort ();
934           wv->selected = !NILP (selected);
936           prev_wv = wv;
938           i += MENU_ITEMS_ITEM_LENGTH;
939         }
940     }
941   }
942 #endif
944   if (!NILP (title))
945     {
946       widget_value *wv_title;
947       widget_value *wv_sep = make_widget_value ("--", NULL, false, Qnil);
949       /* Maybe replace this separator with a bitmap or owner-draw item
950          so that it looks better.  Having two separators looks odd.  */
951       wv_sep->next = first_wv->contents;
953 #ifndef HAVE_MULTILINGUAL_MENU
954       if (STRING_MULTIBYTE (title))
955         title = ENCODE_MENU_STRING (title);
956 #endif
957       wv_title = make_widget_value (SSDATA (title), NULL, false, Qnil);
958       wv_title->button_type = BUTTON_TYPE_NONE;
959       wv_title->next = wv_sep;
960       first_wv->contents = wv_title;
961     }
963   pmenu = [[EmacsMenu alloc] initWithTitle:
964                                [NSString stringWithUTF8String: SSDATA (title)]];
965   [pmenu fillWithWidgetValue: first_wv->contents];
966   free_menubar_widget_value_tree (first_wv);
967   unbind_to (specpdl_count, Qnil);
969   popup_activated_flag = 1;
970   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
971   popup_activated_flag = 0;
972   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
974   unblock_input ();
975   return tem;
979 /* ==========================================================================
981     Toolbar: externally-called functions
983    ========================================================================== */
985 void
986 free_frame_tool_bar (struct frame *f)
987 /* --------------------------------------------------------------------------
988     Under NS we just hide the toolbar until it might be needed again.
989    -------------------------------------------------------------------------- */
991   EmacsView *view = FRAME_NS_VIEW (f);
993   NSTRACE ("free_frame_tool_bar");
995   block_input ();
996   view->wait_for_tool_bar = NO;
998   /* Note: This trigger an animation, which calls windowDidResize
999      repeatedly. */
1000   f->output_data.ns->in_animation = 1;
1001   [[view toolbar] setVisible: NO];
1002   f->output_data.ns->in_animation = 0;
1004   unblock_input ();
1007 void
1008 update_frame_tool_bar (struct frame *f)
1009 /* --------------------------------------------------------------------------
1010     Update toolbar contents
1011    -------------------------------------------------------------------------- */
1013   int i, k = 0;
1014   EmacsView *view = FRAME_NS_VIEW (f);
1015   EmacsToolbar *toolbar = [view toolbar];
1016   int oldh;
1018   NSTRACE ("update_frame_tool_bar");
1020   if (view == nil || toolbar == nil) return;
1021   block_input ();
1023   oldh = FRAME_TOOLBAR_HEIGHT (f);
1025 #ifdef NS_IMPL_COCOA
1026   [toolbar clearActive];
1027 #else
1028   [toolbar clearAll];
1029 #endif
1031   /* update EmacsToolbar as in GtkUtils, build items list */
1032   for (i = 0; i < f->n_tool_bar_items; ++i)
1033     {
1034 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1035                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1037       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1038       int idx;
1039       ptrdiff_t img_id;
1040       struct image *img;
1041       Lisp_Object image;
1042       Lisp_Object helpObj;
1043       const char *helpText;
1045       /* Check if this is a separator.  */
1046       if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1047         {
1048           /* Skip separators.  Newer macOS don't show them, and on
1049              GNUstep they are wide as a button, thus overflowing the
1050              toolbar most of the time.  */
1051           continue;
1052         }
1054       /* If image is a vector, choose the image according to the
1055          button state.  */
1056       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1057       if (VECTORP (image))
1058         {
1059           /* NS toolbar auto-computes disabled and selected images */
1060           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1061           eassert (ASIZE (image) >= idx);
1062           image = AREF (image, idx);
1063         }
1064       else
1065         {
1066           idx = -1;
1067         }
1068       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1069       if (NILP (helpObj))
1070         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1071       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1073       /* Ignore invalid image specifications.  */
1074       if (!valid_image_p (image))
1075         {
1076           /* Don't log anything, GNUS makes invalid images all the time.  */
1077           continue;
1078         }
1080       img_id = lookup_image (f, image);
1081       img = IMAGE_FROM_ID (f, img_id);
1082       prepare_image_for_display (f, img);
1084       if (img->load_failed_p || img->pixmap == nil)
1085         {
1086           NSLog (@"Could not prepare toolbar image for display.");
1087           continue;
1088         }
1090       [toolbar addDisplayItemWithImage: img->pixmap
1091                                    idx: k++
1092                                    tag: i
1093                               helpText: helpText
1094                                enabled: enabled_p];
1095 #undef TOOLPROP
1096     }
1098   if (![toolbar isVisible])
1099     {
1100       f->output_data.ns->in_animation = 1;
1101       [toolbar setVisible: YES];
1102       f->output_data.ns->in_animation = 0;
1103     }
1105 #ifdef NS_IMPL_COCOA
1106   if ([toolbar changed])
1107     {
1108       /* inform app that toolbar has changed */
1109       NSDictionary *dict = [toolbar configurationDictionary];
1110       NSMutableDictionary *newDict = [dict mutableCopy];
1111       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1112       id key;
1113       while ((key = [keys nextObject]) != nil)
1114         {
1115           NSObject *val = [dict objectForKey: key];
1116           if ([val isKindOfClass: [NSArray class]])
1117             {
1118               [newDict setObject:
1119                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1120                           forKey: key];
1121               break;
1122             }
1123         }
1124       [toolbar setConfigurationFromDictionary: newDict];
1125       [newDict release];
1126     }
1127 #endif
1129   if (oldh != FRAME_TOOLBAR_HEIGHT (f))
1130     [view updateFrameSize:YES];
1131   if (view->wait_for_tool_bar && FRAME_TOOLBAR_HEIGHT (f) > 0)
1132     {
1133       view->wait_for_tool_bar = NO;
1134       [view setNeedsDisplay: YES];
1135     }
1137   unblock_input ();
1141 /* ==========================================================================
1143     Toolbar: class implementation
1145    ========================================================================== */
1147 @implementation EmacsToolbar
1149 - (instancetype)initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1151   NSTRACE ("[EmacsToolbar initForView: withIdentifier:]");
1153   self = [super initWithIdentifier: identifier];
1154   emacsView = view;
1155   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1156   [self setSizeMode: NSToolbarSizeModeSmall];
1157   [self setDelegate: self];
1158   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1159   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1160   prevIdentifiers = nil;
1161   prevEnablement = enablement = 0L;
1162   return self;
1165 - (void)dealloc
1167   NSTRACE ("[EmacsToolbar dealloc]");
1169   [prevIdentifiers release];
1170   [activeIdentifiers release];
1171   [identifierToItem release];
1172   [super dealloc];
1175 - (void) clearActive
1177   NSTRACE ("[EmacsToolbar clearActive]");
1179   [prevIdentifiers release];
1180   prevIdentifiers = [activeIdentifiers copy];
1181   [activeIdentifiers removeAllObjects];
1182   prevEnablement = enablement;
1183   enablement = 0L;
1186 - (void) clearAll
1188   NSTRACE ("[EmacsToolbar clearAll]");
1190   [self clearActive];
1191   while ([[self items] count] > 0)
1192     [self removeItemAtIndex: 0];
1195 - (BOOL) changed
1197   NSTRACE ("[EmacsToolbar changed]");
1199   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1200     enablement == prevEnablement ? NO : YES;
1203 - (void) addDisplayItemWithImage: (EmacsImage *)img
1204                              idx: (int)idx
1205                              tag: (int)tag
1206                         helpText: (const char *)help
1207                          enabled: (BOOL)enabled
1209   NSTRACE ("[EmacsToolbar addDisplayItemWithImage: ...]");
1211   /* 1) come up w/identifier */
1212   NSString *identifier
1213     = [NSString stringWithFormat: @"%lu", (unsigned long)[img hash]];
1214   [activeIdentifiers addObject: identifier];
1216   /* 2) create / reuse item */
1217   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1218   if (item == nil)
1219     {
1220       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1221                autorelease];
1222       [item setImage: img];
1223       [item setToolTip: [NSString stringWithUTF8String: help]];
1224       [item setTarget: emacsView];
1225       [item setAction: @selector (toolbarClicked:)];
1226       [identifierToItem setObject: item forKey: identifier];
1227     }
1229 #ifdef NS_IMPL_GNUSTEP
1230   [self insertItemWithItemIdentifier: identifier atIndex: idx];
1231 #endif
1233   [item setTag: tag];
1234   [item setEnabled: enabled];
1236   /* 3) update state */
1237   enablement = (enablement << 1) | (enabled == YES);
1240 /* This overrides super's implementation, which automatically sets
1241    all items to enabled state (for some reason). */
1242 - (void)validateVisibleItems
1244   NSTRACE ("[EmacsToolbar validateVisibleItems]");
1248 /* delegate methods */
1250 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1251       itemForItemIdentifier: (NSString *)itemIdentifier
1252   willBeInsertedIntoToolbar: (BOOL)flag
1254   NSTRACE ("[EmacsToolbar toolbar: ...]");
1256   /* look up NSToolbarItem by identifier and return... */
1257   return [identifierToItem objectForKey: itemIdentifier];
1260 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1262   NSTRACE ("[EmacsToolbar toolbarDefaultItemIdentifiers:]");
1264   /* return entire set.. */
1265   return activeIdentifiers;
1268 /* for configuration palette (not yet supported) */
1269 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1271   NSTRACE ("[EmacsToolbar toolbarAllowedItemIdentifiers:]");
1273   /* return entire set... */
1274   return activeIdentifiers;
1275   //return [identifierToItem allKeys];
1278 - (void)setVisible:(BOOL)shown
1280   NSTRACE ("[EmacsToolbar setVisible:%d]", shown);
1282   [super setVisible:shown];
1286 /* optional and unneeded */
1287 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1288 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1289 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1291 @end  /* EmacsToolbar */
1295 /* ==========================================================================
1297     Tooltip: class implementation
1299    ========================================================================== */
1301 /* Needed because NeXTstep does not provide enough control over tooltip
1302    display. */
1303 @implementation EmacsTooltip
1305 - (instancetype)init
1307   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1308                                             blue: 0.792 alpha: 0.95];
1309   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1310   NSFont *sfont = [font screenFont];
1311   int height = [sfont ascender] - [sfont descender];
1312 /*[font boundingRectForFont].size.height; */
1313   NSRect r = NSMakeRect (0, 0, 100, height+6);
1315   textField = [[NSTextField alloc] initWithFrame: r];
1316   [textField setFont: font];
1317   [textField setBackgroundColor: col];
1319   [textField setEditable: NO];
1320   [textField setSelectable: NO];
1321   [textField setBordered: NO];
1322   [textField setBezeled: NO];
1323   [textField setDrawsBackground: YES];
1325   win = [[NSWindow alloc]
1326             initWithContentRect: [textField frame]
1327                       styleMask: 0
1328                         backing: NSBackingStoreBuffered
1329                           defer: YES];
1330   [win setHasShadow: YES];
1331   [win setReleasedWhenClosed: NO];
1332   [win setDelegate: self];
1333   [[win contentView] addSubview: textField];
1334 /*  [win setBackgroundColor: col]; */
1335   [win setOpaque: NO];
1337   return self;
1340 - (void) dealloc
1342   [win close];
1343   [win release];
1344   [textField release];
1345   [super dealloc];
1348 - (void) setText: (char *)text
1350   NSString *str = [NSString stringWithUTF8String: text];
1351   NSRect r  = [textField frame];
1352   NSSize tooltipDims;
1354   [textField setStringValue: str];
1355   tooltipDims = [[textField cell] cellSize];
1357   r.size.width = tooltipDims.width;
1358   r.size.height = tooltipDims.height;
1359   [textField setFrame: r];
1362 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1364   NSRect wr = [win frame];
1366   wr.origin = NSMakePoint (x, y);
1367   wr.size = [textField frame].size;
1369   [win setFrame: wr display: YES];
1370   [win setLevel: NSPopUpMenuWindowLevel];
1371   [win orderFront: self];
1372   [win display];
1373   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1374                                          selector: @selector (hide)
1375                                          userInfo: nil repeats: NO];
1376   [timer retain];
1379 - (void) hide
1381   [win close];
1382   if (timer != nil)
1383     {
1384       if ([timer isValid])
1385         [timer invalidate];
1386       [timer release];
1387       timer = nil;
1388     }
1391 - (BOOL) isActive
1393   return timer != nil;
1396 - (NSRect) frame
1398   return [textField frame];
1401 @end  /* EmacsTooltip */
1405 /* ==========================================================================
1407     Popup Dialog: implementing functions
1409    ========================================================================== */
1411 static void
1412 pop_down_menu (void *arg)
1414   EmacsDialogPanel *panel = arg;
1416   if (popup_activated_flag)
1417     {
1418       block_input ();
1419       popup_activated_flag = 0;
1420       [panel close];
1421       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1422       unblock_input ();
1423     }
1427 Lisp_Object
1428 ns_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
1430   id dialog;
1431   Lisp_Object tem, title;
1432   NSPoint p;
1433   BOOL isQ;
1435   NSTRACE ("ns_popup_dialog");
1437   isQ = NILP (header);
1439   check_window_system (f);
1441   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1442   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1444   title = Fcar (contents);
1445   CHECK_STRING (title);
1447   if (NILP (Fcar (Fcdr (contents))))
1448     /* No buttons specified, add an "Ok" button so users can pop down
1449        the dialog.  */
1450     contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1452   block_input ();
1453   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1454                                            isQuestion: isQ];
1456   {
1457     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1459     record_unwind_protect_ptr (pop_down_menu, dialog);
1460     popup_activated_flag = 1;
1461     tem = [dialog runDialogAt: p];
1462     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1463   }
1465   unblock_input ();
1467   return tem;
1471 /* ==========================================================================
1473     Popup Dialog: class implementation
1475    ========================================================================== */
1477 @interface FlippedView : NSView
1480 @end
1482 @implementation FlippedView
1483 - (BOOL)isFlipped
1485   return YES;
1487 @end
1489 @implementation EmacsDialogPanel
1491 #define SPACER          8.0
1492 #define ICONSIZE        64.0
1493 #define TEXTHEIGHT      20.0
1494 #define MINCELLWIDTH    90.0
1496 - (instancetype)initWithContentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)aStyle
1497               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1499   NSSize spacing = {SPACER, SPACER};
1500   NSRect area;
1501   id cell;
1502   NSImageView *imgView;
1503   FlippedView *contentView;
1504   NSImage *img;
1506   dialog_return   = Qundefined;
1507   button_values   = NULL;
1508   area.origin.x   = 3*SPACER;
1509   area.origin.y   = 2*SPACER;
1510   area.size.width = ICONSIZE;
1511   area.size.height= ICONSIZE;
1512   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1513   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1514   imgView = [[NSImageView alloc] initWithFrame: area];
1515   [imgView setImage: img];
1516   [imgView setEditable: NO];
1517   [img autorelease];
1518   [imgView autorelease];
1520   aStyle = NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskUtilityWindow;
1521   flag = YES;
1522   rows = 0;
1523   cols = 1;
1524   [super initWithContentRect: contentRect styleMask: aStyle
1525                      backing: backingType defer: flag];
1526   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1527   [contentView autorelease];
1529   [self setContentView: contentView];
1531   [[self contentView] setAutoresizesSubviews: YES];
1533   [[self contentView] addSubview: imgView];
1534   [self setTitle: @""];
1536   area.origin.x   += ICONSIZE+2*SPACER;
1537 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1538   area.size.width = 400;
1539   area.size.height= TEXTHEIGHT;
1540   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1541   [[self contentView] addSubview: command];
1542   [command setStringValue: ns_app_name];
1543   [command setDrawsBackground: NO];
1544   [command setBezeled: NO];
1545   [command setSelectable: NO];
1546   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1548 /*  area.origin.x   = ICONSIZE+2*SPACER;
1549   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1550   area.size.width = 400;
1551   area.size.height= 2;
1552   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1553   [[self contentView] addSubview: tem];
1554   [tem setTitlePosition: NSNoTitle];
1555   [tem setAutoresizingMask: NSViewWidthSizable];*/
1557 /*  area.origin.x = ICONSIZE+2*SPACER; */
1558   area.origin.y += TEXTHEIGHT+SPACER;
1559   area.size.width = 400;
1560   area.size.height= TEXTHEIGHT;
1561   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1562   [[self contentView] addSubview: title];
1563   [title setDrawsBackground: NO];
1564   [title setBezeled: NO];
1565   [title setSelectable: NO];
1566   [title setFont: [NSFont systemFontOfSize: 11.0]];
1568   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1569   [cell setBordered: NO];
1570   [cell setEnabled: NO];
1571   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1572   [cell setBezelStyle: NSRoundedBezelStyle];
1574   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1575                                       mode: NSHighlightModeMatrix
1576                                  prototype: cell
1577                               numberOfRows: 0
1578                            numberOfColumns: 1];
1579   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1580                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1581   [matrix setIntercellSpacing: spacing];
1582   [matrix autorelease];
1584   [[self contentView] addSubview: matrix];
1585   [self setOneShot: YES];
1586   [self setReleasedWhenClosed: YES];
1587   [self setHidesOnDeactivate: YES];
1588   return self;
1592 - (BOOL)windowShouldClose: (id)sender
1594   window_closed = YES;
1595   [NSApp stop:self];
1596   return NO;
1599 - (void)dealloc
1601   xfree (button_values);
1602   [super dealloc];
1605 - (void)process_dialog: (Lisp_Object) list
1607   Lisp_Object item, lst = list;
1608   int row = 0;
1609   int buttons = 0, btnnr = 0;
1611   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1612     {
1613       item = XCAR (list);
1614       if (XTYPE (item) == Lisp_Cons)
1615         ++buttons;
1616     }
1618   if (buttons > 0)
1619     button_values = xmalloc (buttons * sizeof *button_values);
1621   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1622     {
1623       item = XCAR (list);
1624       if (XTYPE (item) == Lisp_String)
1625         {
1626           [self addString: SSDATA (item) row: row++];
1627         }
1628       else if (XTYPE (item) == Lisp_Cons)
1629         {
1630           button_values[btnnr] = XCDR (item);
1631           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1632           ++btnnr;
1633         }
1634       else if (NILP (item))
1635         {
1636           [self addSplit];
1637           row = 0;
1638         }
1639     }
1643 - (void)addButton: (char *)str value: (int)tag row: (int)row
1645   id cell;
1647   if (row >= rows)
1648     {
1649       [matrix addRow];
1650       rows++;
1651     }
1652   cell = [matrix cellAtRow: row column: cols-1];
1653   [cell setTarget: self];
1654   [cell setAction: @selector (clicked: )];
1655   [cell setTitle: [NSString stringWithUTF8String: str]];
1656   [cell setTag: tag];
1657   [cell setBordered: YES];
1658   [cell setEnabled: YES];
1662 - (void)addString: (char *)str row: (int)row
1664   id cell;
1666   if (row >= rows)
1667     {
1668       [matrix addRow];
1669       rows++;
1670     }
1671   cell = [matrix cellAtRow: row column: cols-1];
1672   [cell setTitle: [NSString stringWithUTF8String: str]];
1673   [cell setBordered: YES];
1674   [cell setEnabled: NO];
1678 - (void)addSplit
1680   [matrix addColumn];
1681   cols++;
1685 - (void)clicked: sender
1687   NSArray *sellist = nil;
1688   EMACS_INT seltag;
1690   sellist = [sender selectedCells];
1691   if ([sellist count] < 1)
1692     return;
1694   seltag = [[sellist objectAtIndex: 0] tag];
1695   dialog_return = button_values[seltag];
1696   [NSApp stop:self];
1700 - (instancetype)initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1702   Lisp_Object head;
1703   [super init];
1705   if (XTYPE (contents) == Lisp_Cons)
1706     {
1707       head = Fcar (contents);
1708       [self process_dialog: Fcdr (contents)];
1709     }
1710   else
1711     head = contents;
1713   if (XTYPE (head) == Lisp_String)
1714       [title setStringValue:
1715                  [NSString stringWithUTF8String: SSDATA (head)]];
1716   else if (isQ == YES)
1717       [title setStringValue: @"Question"];
1718   else
1719       [title setStringValue: @"Information"];
1721   {
1722     int i;
1723     NSRect r, s, t;
1725     if (cols == 1 && rows > 1)  /* Never told where to split */
1726       {
1727         [matrix addColumn];
1728         for (i = 0; i < rows/2; i++)
1729           {
1730             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1731                       atRow: i column: 1];
1732             [matrix removeRow: (rows+1)/2];
1733           }
1734       }
1736     [matrix sizeToFit];
1737     {
1738       NSSize csize = [matrix cellSize];
1739       if (csize.width < MINCELLWIDTH)
1740         {
1741           csize.width = MINCELLWIDTH;
1742           [matrix setCellSize: csize];
1743           [matrix sizeToCells];
1744         }
1745     }
1747     [title sizeToFit];
1748     [command sizeToFit];
1750     t = [matrix frame];
1751     r = [title frame];
1752     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1753       {
1754         t.origin.x   = r.origin.x;
1755         t.size.width = r.size.width;
1756       }
1757     r = [command frame];
1758     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1759       {
1760         t.origin.x   = r.origin.x;
1761         t.size.width = r.size.width;
1762       }
1764     r = [self frame];
1765     s = [(NSView *)[self contentView] frame];
1766     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1767     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1768     [self setFrame: r display: NO];
1769   }
1771   return self;
1776 - (void)timeout_handler: (NSTimer *)timedEntry
1778   NSEvent *nxev = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
1779                             location: NSMakePoint (0, 0)
1780                        modifierFlags: 0
1781                            timestamp: 0
1782                         windowNumber: [[NSApp mainWindow] windowNumber]
1783                              context: [NSApp context]
1784                              subtype: 0
1785                                data1: 0
1786                                data2: 0];
1788   timer_fired = YES;
1789   /* We use sto because stopModal/abortModal out of the main loop does not
1790      seem to work in 10.6.  But as we use stop we must send a real event so
1791      the stop is seen and acted upon.  */
1792   [NSApp stop:self];
1793   [NSApp postEvent: nxev atStart: NO];
1796 - (Lisp_Object)runDialogAt: (NSPoint)p
1798   Lisp_Object ret = Qundefined;
1800   while (popup_activated_flag)
1801     {
1802       NSTimer *tmo = nil;
1803       struct timespec next_time = timer_check ();
1805       if (timespec_valid_p (next_time))
1806         {
1807           double time = timespectod (next_time);
1808           tmo = [NSTimer timerWithTimeInterval: time
1809                                         target: self
1810                                       selector: @selector (timeout_handler:)
1811                                       userInfo: 0
1812                                        repeats: NO];
1813           [[NSRunLoop currentRunLoop] addTimer: tmo
1814                                        forMode: NSModalPanelRunLoopMode];
1815         }
1816       timer_fired = NO;
1817       dialog_return = Qundefined;
1818       [NSApp runModalForWindow: self];
1819       ret = dialog_return;
1820       if (! timer_fired)
1821         {
1822           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1823           break;
1824         }
1825     }
1827   if (EQ (ret, Qundefined) && window_closed)
1828     /* Make close button pressed equivalent to C-g.  */
1829     quit ();
1831   return ret;
1834 @end
1837 /* ==========================================================================
1839     Lisp definitions
1841    ========================================================================== */
1843 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1844        doc: /* Cause the NS menu to be re-calculated.  */)
1845      (void)
1847   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1848   return Qnil;
1852 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1853        doc: /* Return t if a menu or popup dialog is active.  */)
1854      (void)
1856   return popup_activated () ? Qt : Qnil;
1859 /* ==========================================================================
1861     Lisp interface declaration
1863    ========================================================================== */
1865 void
1866 syms_of_nsmenu (void)
1868 #ifndef NS_IMPL_COCOA
1869   /* Don't know how to keep track of this in Next/Open/GNUstep.  Always
1870      update menus there.  */
1871   trackingMenu = 1;
1872 #endif
1873   defsubr (&Sns_reset_menu);
1874   defsubr (&Smenu_or_popup_active_p);
1876   DEFSYM (Qdebug_on_next_call, "debug-on-next-call");