Merge branch 'master' into comment-cache
[emacs.git] / src / nsmenu.m
blob5c6442ad0e8f1c7df186090b59590b34fb1294ca
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 - 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 - 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: [e context]
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   FRAME_TOOLBAR_HEIGHT (f) = 0;
1000   /* Note: This trigger an animation, which calls windowDidResize
1001      repeatedly. */
1002   f->output_data.ns->in_animation = 1;
1003   [[view toolbar] setVisible: NO];
1004   f->output_data.ns->in_animation = 0;
1006   unblock_input ();
1009 void
1010 update_frame_tool_bar (struct frame *f)
1011 /* --------------------------------------------------------------------------
1012     Update toolbar contents
1013    -------------------------------------------------------------------------- */
1015   int i, k = 0;
1016   EmacsView *view = FRAME_NS_VIEW (f);
1017   NSWindow *window = [view window];
1018   EmacsToolbar *toolbar = [view toolbar];
1019   int oldh;
1021   NSTRACE ("update_frame_tool_bar");
1023   if (view == nil || toolbar == nil) return;
1024   block_input ();
1026   oldh = FRAME_TOOLBAR_HEIGHT (f);
1028 #ifdef NS_IMPL_COCOA
1029   [toolbar clearActive];
1030 #else
1031   [toolbar clearAll];
1032 #endif
1034   /* update EmacsToolbar as in GtkUtils, build items list */
1035   for (i = 0; i < f->n_tool_bar_items; ++i)
1036     {
1037 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1038                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1040       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1041       int idx;
1042       ptrdiff_t img_id;
1043       struct image *img;
1044       Lisp_Object image;
1045       Lisp_Object helpObj;
1046       const char *helpText;
1048       /* Check if this is a separator.  */
1049       if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1050         {
1051           /* Skip separators.  Newer macOS don't show them, and on
1052              GNUstep they are wide as a button, thus overflowing the
1053              toolbar most of the time.  */
1054           continue;
1055         }
1057       /* If image is a vector, choose the image according to the
1058          button state.  */
1059       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1060       if (VECTORP (image))
1061         {
1062           /* NS toolbar auto-computes disabled and selected images */
1063           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1064           eassert (ASIZE (image) >= idx);
1065           image = AREF (image, idx);
1066         }
1067       else
1068         {
1069           idx = -1;
1070         }
1071       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1072       if (NILP (helpObj))
1073         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1074       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1076       /* Ignore invalid image specifications.  */
1077       if (!valid_image_p (image))
1078         {
1079           /* Don't log anything, GNUS makes invalid images all the time.  */
1080           continue;
1081         }
1083       img_id = lookup_image (f, image);
1084       img = IMAGE_FROM_ID (f, img_id);
1085       prepare_image_for_display (f, img);
1087       if (img->load_failed_p || img->pixmap == nil)
1088         {
1089           NSLog (@"Could not prepare toolbar image for display.");
1090           continue;
1091         }
1093       [toolbar addDisplayItemWithImage: img->pixmap
1094                                    idx: k++
1095                                    tag: i
1096                               helpText: helpText
1097                                enabled: enabled_p];
1098 #undef TOOLPROP
1099     }
1101   if (![toolbar isVisible])
1102     {
1103       f->output_data.ns->in_animation = 1;
1104       [toolbar setVisible: YES];
1105       f->output_data.ns->in_animation = 0;
1106     }
1108 #ifdef NS_IMPL_COCOA
1109   if ([toolbar changed])
1110     {
1111       /* inform app that toolbar has changed */
1112       NSDictionary *dict = [toolbar configurationDictionary];
1113       NSMutableDictionary *newDict = [dict mutableCopy];
1114       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1115       id key;
1116       while ((key = [keys nextObject]) != nil)
1117         {
1118           NSObject *val = [dict objectForKey: key];
1119           if ([val isKindOfClass: [NSArray class]])
1120             {
1121               [newDict setObject:
1122                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1123                           forKey: key];
1124               break;
1125             }
1126         }
1127       [toolbar setConfigurationFromDictionary: newDict];
1128       [newDict release];
1129     }
1130 #endif
1132   FRAME_TOOLBAR_HEIGHT (f) =
1133     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1134     - FRAME_NS_TITLEBAR_HEIGHT (f);
1135   if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1136     FRAME_TOOLBAR_HEIGHT (f) = 0;
1138   if (oldh != FRAME_TOOLBAR_HEIGHT (f))
1139     [view updateFrameSize:YES];
1140   if (view->wait_for_tool_bar && FRAME_TOOLBAR_HEIGHT (f) > 0)
1141     {
1142       view->wait_for_tool_bar = NO;
1143       [view setNeedsDisplay: YES];
1144     }
1146   unblock_input ();
1150 /* ==========================================================================
1152     Toolbar: class implementation
1154    ========================================================================== */
1156 @implementation EmacsToolbar
1158 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1160   NSTRACE ("[EmacsToolbar initForView: withIdentifier:]");
1162   self = [super initWithIdentifier: identifier];
1163   emacsView = view;
1164   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1165   [self setSizeMode: NSToolbarSizeModeSmall];
1166   [self setDelegate: self];
1167   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1168   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1169   prevIdentifiers = nil;
1170   prevEnablement = enablement = 0L;
1171   return self;
1174 - (void)dealloc
1176   NSTRACE ("[EmacsToolbar dealloc]");
1178   [prevIdentifiers release];
1179   [activeIdentifiers release];
1180   [identifierToItem release];
1181   [super dealloc];
1184 - (void) clearActive
1186   NSTRACE ("[EmacsToolbar clearActive]");
1188   [prevIdentifiers release];
1189   prevIdentifiers = [activeIdentifiers copy];
1190   [activeIdentifiers removeAllObjects];
1191   prevEnablement = enablement;
1192   enablement = 0L;
1195 - (void) clearAll
1197   NSTRACE ("[EmacsToolbar clearAll]");
1199   [self clearActive];
1200   while ([[self items] count] > 0)
1201     [self removeItemAtIndex: 0];
1204 - (BOOL) changed
1206   NSTRACE ("[EmacsToolbar changed]");
1208   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1209     enablement == prevEnablement ? NO : YES;
1212 - (void) addDisplayItemWithImage: (EmacsImage *)img
1213                              idx: (int)idx
1214                              tag: (int)tag
1215                         helpText: (const char *)help
1216                          enabled: (BOOL)enabled
1218   NSTRACE ("[EmacsToolbar addDisplayItemWithImage: ...]");
1220   /* 1) come up w/identifier */
1221   NSString *identifier
1222     = [NSString stringWithFormat: @"%lu", (unsigned long)[img hash]];
1223   [activeIdentifiers addObject: identifier];
1225   /* 2) create / reuse item */
1226   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1227   if (item == nil)
1228     {
1229       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1230                autorelease];
1231       [item setImage: img];
1232       [item setToolTip: [NSString stringWithUTF8String: help]];
1233       [item setTarget: emacsView];
1234       [item setAction: @selector (toolbarClicked:)];
1235       [identifierToItem setObject: item forKey: identifier];
1236     }
1238 #ifdef NS_IMPL_GNUSTEP
1239   [self insertItemWithItemIdentifier: identifier atIndex: idx];
1240 #endif
1242   [item setTag: tag];
1243   [item setEnabled: enabled];
1245   /* 3) update state */
1246   enablement = (enablement << 1) | (enabled == YES);
1249 /* This overrides super's implementation, which automatically sets
1250    all items to enabled state (for some reason). */
1251 - (void)validateVisibleItems
1253   NSTRACE ("[EmacsToolbar validateVisibleItems]");
1257 /* delegate methods */
1259 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1260       itemForItemIdentifier: (NSString *)itemIdentifier
1261   willBeInsertedIntoToolbar: (BOOL)flag
1263   NSTRACE ("[EmacsToolbar toolbar: ...]");
1265   /* look up NSToolbarItem by identifier and return... */
1266   return [identifierToItem objectForKey: itemIdentifier];
1269 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1271   NSTRACE ("[EmacsToolbar toolbarDefaultItemIdentifiers:]");
1273   /* return entire set.. */
1274   return activeIdentifiers;
1277 /* for configuration palette (not yet supported) */
1278 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1280   NSTRACE ("[EmacsToolbar toolbarAllowedItemIdentifiers:]");
1282   /* return entire set... */
1283   return activeIdentifiers;
1284   //return [identifierToItem allKeys];
1287 - (void)setVisible:(BOOL)shown
1289   NSTRACE ("[EmacsToolbar setVisible:%d]", shown);
1291   [super setVisible:shown];
1295 /* optional and unneeded */
1296 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1297 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1298 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1300 @end  /* EmacsToolbar */
1304 /* ==========================================================================
1306     Tooltip: class implementation
1308    ========================================================================== */
1310 /* Needed because NeXTstep does not provide enough control over tooltip
1311    display. */
1312 @implementation EmacsTooltip
1314 - init
1316   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1317                                             blue: 0.792 alpha: 0.95];
1318   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1319   NSFont *sfont = [font screenFont];
1320   int height = [sfont ascender] - [sfont descender];
1321 /*[font boundingRectForFont].size.height; */
1322   NSRect r = NSMakeRect (0, 0, 100, height+6);
1324   textField = [[NSTextField alloc] initWithFrame: r];
1325   [textField setFont: font];
1326   [textField setBackgroundColor: col];
1328   [textField setEditable: NO];
1329   [textField setSelectable: NO];
1330   [textField setBordered: NO];
1331   [textField setBezeled: NO];
1332   [textField setDrawsBackground: YES];
1334   win = [[NSWindow alloc]
1335             initWithContentRect: [textField frame]
1336                       styleMask: 0
1337                         backing: NSBackingStoreBuffered
1338                           defer: YES];
1339   [win setHasShadow: YES];
1340   [win setReleasedWhenClosed: NO];
1341   [win setDelegate: self];
1342   [[win contentView] addSubview: textField];
1343 /*  [win setBackgroundColor: col]; */
1344   [win setOpaque: NO];
1346   return self;
1349 - (void) dealloc
1351   [win close];
1352   [win release];
1353   [textField release];
1354   [super dealloc];
1357 - (void) setText: (char *)text
1359   NSString *str = [NSString stringWithUTF8String: text];
1360   NSRect r  = [textField frame];
1361   NSSize tooltipDims;
1363   [textField setStringValue: str];
1364   tooltipDims = [[textField cell] cellSize];
1366   r.size.width = tooltipDims.width;
1367   r.size.height = tooltipDims.height;
1368   [textField setFrame: r];
1371 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1373   NSRect wr = [win frame];
1375   wr.origin = NSMakePoint (x, y);
1376   wr.size = [textField frame].size;
1378   [win setFrame: wr display: YES];
1379   [win setLevel: NSPopUpMenuWindowLevel];
1380   [win orderFront: self];
1381   [win display];
1382   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1383                                          selector: @selector (hide)
1384                                          userInfo: nil repeats: NO];
1385   [timer retain];
1388 - (void) hide
1390   [win close];
1391   if (timer != nil)
1392     {
1393       if ([timer isValid])
1394         [timer invalidate];
1395       [timer release];
1396       timer = nil;
1397     }
1400 - (BOOL) isActive
1402   return timer != nil;
1405 - (NSRect) frame
1407   return [textField frame];
1410 @end  /* EmacsTooltip */
1414 /* ==========================================================================
1416     Popup Dialog: implementing functions
1418    ========================================================================== */
1420 static void
1421 pop_down_menu (void *arg)
1423   EmacsDialogPanel *panel = arg;
1425   if (popup_activated_flag)
1426     {
1427       block_input ();
1428       popup_activated_flag = 0;
1429       [panel close];
1430       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1431       unblock_input ();
1432     }
1436 Lisp_Object
1437 ns_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
1439   id dialog;
1440   Lisp_Object tem, title;
1441   NSPoint p;
1442   BOOL isQ;
1444   NSTRACE ("ns_popup_dialog");
1446   isQ = NILP (header);
1448   check_window_system (f);
1450   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1451   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1453   title = Fcar (contents);
1454   CHECK_STRING (title);
1456   if (NILP (Fcar (Fcdr (contents))))
1457     /* No buttons specified, add an "Ok" button so users can pop down
1458        the dialog.  */
1459     contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1461   block_input ();
1462   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1463                                            isQuestion: isQ];
1465   {
1466     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1468     record_unwind_protect_ptr (pop_down_menu, dialog);
1469     popup_activated_flag = 1;
1470     tem = [dialog runDialogAt: p];
1471     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1472   }
1474   unblock_input ();
1476   return tem;
1480 /* ==========================================================================
1482     Popup Dialog: class implementation
1484    ========================================================================== */
1486 @interface FlippedView : NSView
1489 @end
1491 @implementation FlippedView
1492 - (BOOL)isFlipped
1494   return YES;
1496 @end
1498 @implementation EmacsDialogPanel
1500 #define SPACER          8.0
1501 #define ICONSIZE        64.0
1502 #define TEXTHEIGHT      20.0
1503 #define MINCELLWIDTH    90.0
1505 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1506               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1508   NSSize spacing = {SPACER, SPACER};
1509   NSRect area;
1510   id cell;
1511   NSImageView *imgView;
1512   FlippedView *contentView;
1513   NSImage *img;
1515   dialog_return   = Qundefined;
1516   button_values   = NULL;
1517   area.origin.x   = 3*SPACER;
1518   area.origin.y   = 2*SPACER;
1519   area.size.width = ICONSIZE;
1520   area.size.height= ICONSIZE;
1521   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1522 #ifdef NS_IMPL_COCOA
1523 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
1524   [img setScalesWhenResized: YES];
1525 #endif
1526 #endif
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|NSUtilityWindowMask;
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 - 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");