Fix Bug#24432
[emacs.git] / src / nsmenu.m
blobea9f790990f2e56a79a5a992116b15cf0b02d9e6
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007-2016 Free Software Foundation, Inc.
4 This file is part of GNU Emacs.
6 GNU Emacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or (at
9 your option) any later version.
11 GNU Emacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
20 By Adrian Robert, based on code from original nsmenu.m (Carl Edman,
21 Christian Limpach, Scott Bender, Christophe de Dinechin) and code in the
22 Carbon version by Yamamoto Mitsuharu. */
24 /* This should be the first include, as it may set up #defines affecting
25    interpretation of even the system includes. */
26 #include <config.h>
28 #include "lisp.h"
29 #include "window.h"
30 #include "character.h"
31 #include "buffer.h"
32 #include "keymap.h"
33 #include "coding.h"
34 #include "commands.h"
35 #include "blockinput.h"
36 #include "nsterm.h"
37 #include "termhooks.h"
38 #include "keyboard.h"
39 #include "menu.h"
41 #define NSMENUPROFILE 0
43 #if NSMENUPROFILE
44 #include <sys/timeb.h>
45 #include <sys/types.h>
46 #endif
49 #if 0
50 /* Include lisp -> C common menu parsing code */
51 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
52 #include "nsmenu_common.c"
53 #endif
55 extern long context_menu_value;
56 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
58 /* Nonzero means a menu is currently active.  */
59 static int popup_activated_flag;
61 /* Nonzero means we are tracking and updating menus.  */
62 static int trackingMenu;
65 /* NOTE: toolbar implementation is at end,
66   following complete menu implementation. */
69 /* ==========================================================================
71     Menu: Externally-called functions
73    ========================================================================== */
76 /* Supposed to discard menubar and free storage.  Since we share the
77    menubar among frames and update its context for the focused window,
78    there is nothing to do here. */
79 void
80 free_frame_menubar (struct frame *f)
82   return;
86 int
87 popup_activated (void)
89   return popup_activated_flag;
93 /* --------------------------------------------------------------------------
94     Update menubar.  Three cases:
95     1) ! deep_p, submenu = nil: Fresh switch onto a frame -- either set up
96        just top-level menu strings (OS X), or goto case (2) (GNUstep).
97     2) deep_p, submenu = nil: Recompute all submenus.
98     3) deep_p, submenu = non-nil: Update contents of a single submenu.
99    -------------------------------------------------------------------------- */
100 static void
101 ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu)
103   NSAutoreleasePool *pool;
104   id menu = [NSApp mainMenu];
105   static EmacsMenu *last_submenu = nil;
106   BOOL needsSet = NO;
107   bool owfi;
108   Lisp_Object items;
109   widget_value *wv, *first_wv, *prev_wv = 0;
110   int i;
112 #if NSMENUPROFILE
113   struct timeb tb;
114   long t;
115 #endif
117   NSTRACE ("ns_update_menubar");
119   if (f != SELECTED_FRAME ())
120       return;
121   XSETFRAME (Vmenu_updating_frame, f);
122 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
124   block_input ();
125   pool = [[NSAutoreleasePool alloc] init];
127   /* Menu may have been created automatically; if so, discard it. */
128   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
129     {
130       [menu release];
131       menu = nil;
132     }
134   if (menu == nil)
135     {
136       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
137       needsSet = YES;
138     }
140 #if NSMENUPROFILE
141   ftime (&tb);
142   t = -(1000*tb.time+tb.millitm);
143 #endif
145 #ifdef NS_IMPL_GNUSTEP
146   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
147 #endif
149   if (deep_p)
150     {
151       /* Fully parse one or more of the submenus. */
152       int n = 0;
153       int *submenu_start, *submenu_end;
154       bool *submenu_top_level_items;
155       int *submenu_n_panes;
156       struct buffer *prev = current_buffer;
157       Lisp_Object buffer;
158       ptrdiff_t specpdl_count = SPECPDL_INDEX ();
159       int previous_menu_items_used = f->menu_bar_items_used;
160       Lisp_Object *previous_items
161         = alloca (previous_menu_items_used * sizeof *previous_items);
163       /* lisp preliminaries */
164       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->contents;
165       specbind (Qinhibit_quit, Qt);
166       specbind (Qdebug_on_next_call, Qnil);
167       record_unwind_save_match_data ();
168       if (NILP (Voverriding_local_map_menu_flag))
169         {
170           specbind (Qoverriding_terminal_local_map, Qnil);
171           specbind (Qoverriding_local_map, Qnil);
172         }
173       set_buffer_internal_1 (XBUFFER (buffer));
175       /* TODO: for some reason this is not needed in other terms,
176            but some menu updates call Info-extract-pointer which causes
177            abort-on-error if waiting-for-input.  Needs further investigation. */
178       owfi = waiting_for_input;
179       waiting_for_input = 0;
181       /* lucid hook and possible reset */
182       safe_run_hooks (Qactivate_menubar_hook);
183       if (! NILP (Vlucid_menu_bar_dirty_flag))
184         call0 (Qrecompute_lucid_menubar);
185       safe_run_hooks (Qmenu_bar_update_hook);
186       fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
188       /* Now ready to go */
189       items = FRAME_MENU_BAR_ITEMS (f);
191       /* Save the frame's previous menu bar contents data */
192       if (previous_menu_items_used)
193         memcpy (previous_items, aref_addr (f->menu_bar_vector, 0),
194                 previous_menu_items_used * sizeof (Lisp_Object));
196       /* parse stage 1: extract from lisp */
197       save_menu_items ();
199       menu_items = f->menu_bar_vector;
200       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
201       submenu_start = alloca (ASIZE (items) * sizeof *submenu_start);
202       submenu_end = alloca (ASIZE (items) * sizeof *submenu_end);
203       submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes);
204       submenu_top_level_items = alloca (ASIZE (items)
205                                         * sizeof *submenu_top_level_items);
206       init_menu_items ();
207       for (i = 0; i < ASIZE (items); i += 4)
208         {
209           Lisp_Object key, string, maps;
211           key = AREF (items, i);
212           string = AREF (items, i + 1);
213           maps = AREF (items, i + 2);
214           if (NILP (string))
215             break;
217           /* FIXME: we'd like to only parse the needed submenu, but this
218                was causing crashes in the _common parsing code.. need to make
219                sure proper initialization done.. */
220 /*        if (submenu && strcmp ([[submenu title] UTF8String], SSDATA (string)))
221              continue; */
223           submenu_start[i] = menu_items_used;
225           menu_items_n_panes = 0;
226           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
227           submenu_n_panes[i] = menu_items_n_panes;
228           submenu_end[i] = menu_items_used;
229           n++;
230         }
232       finish_menu_items ();
233       waiting_for_input = owfi;
236       if (submenu && n == 0)
237         {
238           /* should have found a menu for this one but didn't */
239           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
240                   [[submenu title] UTF8String]);
241           discard_menu_items ();
242           unbind_to (specpdl_count, Qnil);
243           [pool release];
244           unblock_input ();
245           return;
246         }
248       /* parse stage 2: insert into lucid 'widget_value' structures
249          [comments in other terms say not to evaluate lisp code here] */
250       wv = make_widget_value ("menubar", NULL, true, Qnil);
251       wv->button_type = BUTTON_TYPE_NONE;
252       first_wv = wv;
254       for (i = 0; i < 4*n; i += 4)
255         {
256           menu_items_n_panes = submenu_n_panes[i];
257           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
258                                       submenu_top_level_items[i]);
259           if (prev_wv)
260             prev_wv->next = wv;
261           else
262             first_wv->contents = wv;
263           /* Don't set wv->name here; GC during the loop might relocate it.  */
264           wv->enabled = 1;
265           wv->button_type = BUTTON_TYPE_NONE;
266           prev_wv = wv;
267         }
269       set_buffer_internal_1 (prev);
271       /* Compare the new menu items with previous, and leave off if no change */
272       /* FIXME: following other terms here, but seems like this should be
273            done before parse stage 2 above, since its results aren't used */
274       if (previous_menu_items_used
275           && (!submenu || (submenu && submenu == last_submenu))
276           && menu_items_used == previous_menu_items_used)
277         {
278           for (i = 0; i < previous_menu_items_used; i++)
279             /* FIXME: this ALWAYS fails on Buffers menu items.. something
280                  about their strings causes them to change every time, so we
281                  double-check failures */
282             if (!EQ (previous_items[i], AREF (menu_items, i)))
283               if (!(STRINGP (previous_items[i])
284                     && STRINGP (AREF (menu_items, i))
285                     && !strcmp (SSDATA (previous_items[i]),
286                                 SSDATA (AREF (menu_items, i)))))
287                   break;
288           if (i == previous_menu_items_used)
289             {
290               /* No change.. */
292 #if NSMENUPROFILE
293               ftime (&tb);
294               t += 1000*tb.time+tb.millitm;
295               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
296 #endif
298               free_menubar_widget_value_tree (first_wv);
299               discard_menu_items ();
300               unbind_to (specpdl_count, Qnil);
301               [pool release];
302               unblock_input ();
303               return;
304             }
305         }
306       /* The menu items are different, so store them in the frame */
307       /* FIXME: this is not correct for single-submenu case */
308       fset_menu_bar_vector (f, menu_items);
309       f->menu_bar_items_used = menu_items_used;
311       /* Calls restore_menu_items, etc., as they were outside */
312       unbind_to (specpdl_count, Qnil);
314       /* Parse stage 2a: now GC cannot happen during the lifetime of the
315          widget_value, so it's safe to store data from a Lisp_String */
316       wv = first_wv->contents;
317       for (i = 0; i < ASIZE (items); i += 4)
318         {
319           Lisp_Object string;
320           string = AREF (items, i + 1);
321           if (NILP (string))
322             break;
324           wv->name = SSDATA (string);
325           update_submenu_strings (wv->contents);
326           wv = wv->next;
327         }
329       /* Now, update the NS menu; if we have a submenu, use that, otherwise
330          create a new menu for each sub and fill it. */
331       if (submenu)
332         {
333           const char *submenuTitle = [[submenu title] UTF8String];
334           for (wv = first_wv->contents; wv; wv = wv->next)
335             {
336               if (!strcmp (submenuTitle, wv->name))
337                 {
338                   [submenu fillWithWidgetValue: wv->contents];
339                   last_submenu = submenu;
340                   break;
341                 }
342             }
343         }
344       else
345         {
346           [menu fillWithWidgetValue: first_wv->contents frame: f];
347         }
349     }
350   else
351     {
352       static int n_previous_strings = 0;
353       static char previous_strings[100][10];
354       static struct frame *last_f = NULL;
355       int n;
356       Lisp_Object string;
358       wv = make_widget_value ("menubar", NULL, true, Qnil);
359       wv->button_type = BUTTON_TYPE_NONE;
360       first_wv = wv;
362       /* Make widget-value tree w/ just the top level menu bar strings */
363       items = FRAME_MENU_BAR_ITEMS (f);
364       if (NILP (items))
365         {
366           free_menubar_widget_value_tree (first_wv);
367           [pool release];
368           unblock_input ();
369           return;
370         }
373       /* check if no change.. this mechanism is a bit rough, but ready */
374       n = ASIZE (items) / 4;
375       if (f == last_f && n_previous_strings == n)
376         {
377           for (i = 0; i<n; i++)
378             {
379               string = AREF (items, 4*i+1);
381               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
382                 continue;
383               if (NILP (string))
384                 {
385                   if (previous_strings[i][0])
386                     break;
387                   else
388                     continue;
389                 }
390               else if (memcmp (previous_strings[i], SDATA (string),
391                           min (10, SBYTES (string) + 1)))
392                 break;
393             }
395           if (i == n)
396             {
397               free_menubar_widget_value_tree (first_wv);
398               [pool release];
399               unblock_input ();
400               return;
401             }
402         }
404       [menu clear];
405       for (i = 0; i < ASIZE (items); i += 4)
406         {
407           string = AREF (items, i + 1);
408           if (NILP (string))
409             break;
411           if (n < 100)
412             memcpy (previous_strings[i/4], SDATA (string),
413                     min (10, SBYTES (string) + 1));
415           wv = make_widget_value (SSDATA (string), NULL, true, Qnil);
416           wv->button_type = BUTTON_TYPE_NONE;
417           wv->call_data = (void *) (intptr_t) (-1);
419 #ifdef NS_IMPL_COCOA
420           /* we'll update the real copy under app menu when time comes */
421           if (!strcmp ("Services", wv->name))
422             {
423               /* but we need to make sure it will update on demand */
424               [svcsMenu setFrame: f];
425             }
426           else
427 #endif
428           [menu addSubmenuWithTitle: wv->name forFrame: f];
430           if (prev_wv)
431             prev_wv->next = wv;
432           else
433             first_wv->contents = wv;
434           prev_wv = wv;
435         }
437       last_f = f;
438       if (n < 100)
439         n_previous_strings = n;
440       else
441         n_previous_strings = 0;
443     }
444   free_menubar_widget_value_tree (first_wv);
447 #if NSMENUPROFILE
448   ftime (&tb);
449   t += 1000*tb.time+tb.millitm;
450   fprintf (stderr, "Menu update took %ld msec.\n", t);
451 #endif
453   /* set main menu */
454   if (needsSet)
455     [NSApp setMainMenu: menu];
457   [pool release];
458   unblock_input ();
463 /* Main emacs core entry point for menubar menus: called to indicate that the
464    frame's menus have changed, and the *step representation should be updated
465    from Lisp. */
466 void
467 set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
469   ns_update_menubar (f, deep_p, nil);
472 void
473 x_activate_menubar (struct frame *f)
475 #ifdef NS_IMPL_COCOA
476   ns_update_menubar (f, true, nil);
477   ns_check_pending_open_menu ();
478 #endif
484 /* ==========================================================================
486     Menu: class implementation
488    ========================================================================== */
491 /* Menu that can define itself from Emacs "widget_value"s and will lazily
492    update itself when user clicked.  Based on Carbon/AppKit implementation
493    by Yamamoto Mitsuharu. */
494 @implementation EmacsMenu
496 /* override designated initializer */
497 - initWithTitle: (NSString *)title
499   frame = 0;
500   if ((self = [super initWithTitle: title]))
501     [self setAutoenablesItems: NO];
502   return self;
506 /* used for top-level */
507 - initWithTitle: (NSString *)title frame: (struct frame *)f
509   [self initWithTitle: title];
510   frame = f;
511 #ifdef NS_IMPL_COCOA
512   [self setDelegate: self];
513 #endif
514   return self;
518 - (void)setFrame: (struct frame *)f
520   frame = f;
523 #ifdef NS_IMPL_COCOA
524 -(void)trackingNotification:(NSNotification *)notification
526   /* Update menu in menuNeedsUpdate only while tracking menus.  */
527   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
528                   ? 1 : 0);
529   if (! trackingMenu) ns_check_menu_open (nil);
532 - (void)menuWillOpen:(NSMenu *)menu
534   ++trackingMenu;
536 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
537   // On 10.6 we get repeated calls, only the one for NSSystemDefined is "real".
538   if ([[NSApp currentEvent] type] != NSSystemDefined) return;
539 #endif
541   /* When dragging from one menu to another, we get willOpen followed by didClose,
542      i.e. trackingMenu == 3 in willOpen and then 2 after didClose.
543      We have updated all menus, so avoid doing it when trackingMenu == 3.  */
544   if (trackingMenu == 2)
545     ns_check_menu_open (menu);
548 - (void)menuDidClose:(NSMenu *)menu
550   --trackingMenu;
553 #endif /* NS_IMPL_COCOA */
555 /* delegate method called when a submenu is being opened: run a 'deep' call
556    to set_frame_menubar */
557 - (void)menuNeedsUpdate: (NSMenu *)menu
559   if (!FRAME_LIVE_P (frame))
560     return;
562   /* Cocoa/Carbon will request update on every keystroke
563      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
564      since key equivalents are handled through emacs.
565      On Leopard, even keystroke events generate SystemDefined event.
566      Third-party applications that enhance mouse / trackpad
567      interaction, or also VNC/Remote Desktop will send events
568      of type AppDefined rather than SysDefined.
569      Menus will fail to show up if they haven't been initialized.
570      AppDefined events may lack timing data.
572      Thus, we rely on the didBeginTrackingNotification notification
573      as above to indicate the need for updates.
574      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
575      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
576   */
577   if (trackingMenu == 0)
578     return;
579 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
580 #ifdef NS_IMPL_GNUSTEP
581   /* Don't know how to do this for anything other than OSX >= 10.5
582      This is wrong, as it might run Lisp code in the event loop.  */
583   ns_update_menubar (frame, true, self);
584 #endif
588 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
590   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
591       && FRAME_NS_VIEW (SELECTED_FRAME ()))
592     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
593   return YES;
597 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
598    into an accelerator string.  We are only able to display a single character
599    for an accelerator, together with an optional modifier combination.  (Under
600    Carbon more control was possible, but in Cocoa multi-char strings passed to
601    NSMenuItem get ignored.  For now we try to display a super-single letter
602    combo, and return the others as strings to be appended to the item title.
603    (This is signaled by setting keyEquivModMask to 0 for now.) */
604 -(NSString *)parseKeyEquiv: (const char *)key
606   const char *tpos = key;
607   keyEquivModMask = NSEventModifierFlagCommand;
609   if (!key || !strlen (key))
610     return @"";
612   while (*tpos == ' ' || *tpos == '(')
613     tpos++;
614   if ((*tpos == 's') && (*(tpos+1) == '-'))
615     {
616       return [NSString stringWithFormat: @"%c", tpos[2]];
617     }
618   keyEquivModMask = 0; /* signal */
619   return [NSString stringWithUTF8String: tpos];
623 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
625   NSMenuItem *item;
626   widget_value *wv = (widget_value *)wvptr;
628   if (menu_separator_name_p (wv->name))
629     {
630       item = [NSMenuItem separatorItem];
631       [self addItem: item];
632     }
633   else
634     {
635       NSString *title, *keyEq;
636       title = [NSString stringWithUTF8String: wv->name];
637       if (title == nil)
638         title = @"< ? >";  /* (get out in the open so we know about it) */
640       keyEq = [self parseKeyEquiv: wv->key];
641 #ifdef NS_IMPL_COCOA
642       /* OS X just ignores modifier strings longer than one character */
643       if (keyEquivModMask == 0)
644         title = [title stringByAppendingFormat: @" (%@)", keyEq];
645 #endif
647       item = [self addItemWithTitle: (NSString *)title
648                              action: @selector (menuDown:)
649                       keyEquivalent: keyEq];
650       [item setKeyEquivalentModifierMask: keyEquivModMask];
652       [item setEnabled: wv->enabled];
654       /* Draw radio buttons and tickboxes */
655       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
656                            wv->button_type == BUTTON_TYPE_RADIO))
657         [item setState: NSOnState];
658       else
659         [item setState: NSOffState];
661       [item setTag: (NSInteger)wv->call_data];
662     }
664   return item;
668 /* convenience */
669 -(void)clear
671   int n;
673   for (n = [self numberOfItems]-1; n >= 0; n--)
674     {
675       NSMenuItem *item = [self itemAtIndex: n];
676       NSString *title = [item title];
677       if ([ns_app_name isEqualToString: title]
678           && ![item isSeparatorItem])
679         continue;
680       [self removeItemAtIndex: n];
681     }
685 - (void)fillWithWidgetValue: (void *)wvptr
687   [self fillWithWidgetValue: wvptr frame: (struct frame *)nil];
690 - (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f
692   widget_value *wv = (widget_value *)wvptr;
694   /* clear existing contents */
695   [self clear];
697   /* add new contents */
698   for (; wv != NULL; wv = wv->next)
699     {
700       NSMenuItem *item = [self addItemWithWidgetValue: wv];
702       if (wv->contents)
703         {
704           EmacsMenu *submenu;
706           if (f)
707             submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f];
708           else
709             submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
711           [self setSubmenu: submenu forItem: item];
712           [submenu fillWithWidgetValue: wv->contents];
713           [submenu release];
714           [item setAction: (SEL)nil];
715         }
716     }
718 #ifdef NS_IMPL_GNUSTEP
719   if ([[self window] isVisible])
720     [self sizeToFit];
721 #endif
725 /* adds an empty submenu and returns it */
726 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
728   NSString *titleStr = [NSString stringWithUTF8String: title];
729   NSMenuItem *item = [self addItemWithTitle: titleStr
730                                      action: (SEL)nil /*@selector (menuDown:) */
731                               keyEquivalent: @""];
732   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
733   [self setSubmenu: submenu forItem: item];
734   [submenu release];
735   return submenu;
738 /* run a menu in popup mode */
739 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
740                  keymaps: (bool)keymaps
742   EmacsView *view = FRAME_NS_VIEW (f);
743   NSEvent *e, *event;
744   long retVal;
746 /*   p = [view convertPoint:p fromView: nil]; */
747   p.y = NSHeight ([view frame]) - p.y;
748   e = [[view window] currentEvent];
749    event = [NSEvent mouseEventWithType: NSEventTypeRightMouseDown
750                               location: p
751                          modifierFlags: 0
752                              timestamp: [e timestamp]
753                           windowNumber: [[view window] windowNumber]
754                                context: [e context]
755                            eventNumber: 0/*[e eventNumber] */
756                             clickCount: 1
757                               pressure: 0];
759   context_menu_value = -1;
760   [NSMenu popUpContextMenu: self withEvent: event forView: view];
761   retVal = context_menu_value;
762   context_menu_value = 0;
763   return retVal > 0
764       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
765       : Qnil;
768 @end  /* EmacsMenu */
772 /* ==========================================================================
774     Context Menu: implementing functions
776    ========================================================================== */
778 Lisp_Object
779 ns_menu_show (struct frame *f, int x, int y, int menuflags,
780               Lisp_Object title, const char **error)
782   EmacsMenu *pmenu;
783   NSPoint p;
784   Lisp_Object tem;
785   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
786   widget_value *wv, *first_wv = 0;
787   bool keymaps = (menuflags & MENU_KEYMAPS);
789   NSTRACE ("ns_menu_show");
791   block_input ();
793   p.x = x; p.y = y;
795   /* now parse stage 2 as in ns_update_menubar */
796   wv = make_widget_value ("contextmenu", NULL, true, Qnil);
797   wv->button_type = BUTTON_TYPE_NONE;
798   first_wv = wv;
800 #if 0
801   /* FIXME: a couple of one-line differences prevent reuse */
802   wv = digest_single_submenu (0, menu_items_used, 0);
803 #else
804   {
805   widget_value *save_wv = 0, *prev_wv = 0;
806   widget_value **submenu_stack
807     = alloca (menu_items_used * sizeof *submenu_stack);
808 /*   Lisp_Object *subprefix_stack
809        = alloca (menu_items_used * sizeof *subprefix_stack); */
810   int submenu_depth = 0;
811   int first_pane = 1;
812   int i;
814   /* Loop over all panes and items, filling in the tree.  */
815   i = 0;
816   while (i < menu_items_used)
817     {
818       if (EQ (AREF (menu_items, i), Qnil))
819         {
820           submenu_stack[submenu_depth++] = save_wv;
821           save_wv = prev_wv;
822           prev_wv = 0;
823           first_pane = 1;
824           i++;
825         }
826       else if (EQ (AREF (menu_items, i), Qlambda))
827         {
828           prev_wv = save_wv;
829           save_wv = submenu_stack[--submenu_depth];
830           first_pane = 0;
831           i++;
832         }
833       else if (EQ (AREF (menu_items, i), Qt)
834                && submenu_depth != 0)
835         i += MENU_ITEMS_PANE_LENGTH;
836       /* Ignore a nil in the item list.
837          It's meaningful only for dialog boxes.  */
838       else if (EQ (AREF (menu_items, i), Qquote))
839         i += 1;
840       else if (EQ (AREF (menu_items, i), Qt))
841         {
842           /* Create a new pane.  */
843           Lisp_Object pane_name, prefix;
844           const char *pane_string;
846           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
847           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
849 #ifndef HAVE_MULTILINGUAL_MENU
850           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
851             {
852               pane_name = ENCODE_MENU_STRING (pane_name);
853               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
854             }
855 #endif
856           pane_string = (NILP (pane_name)
857                          ? "" : SSDATA (pane_name));
858           /* If there is just one top-level pane, put all its items directly
859              under the top-level menu.  */
860           if (menu_items_n_panes == 1)
861             pane_string = "";
863           /* If the pane has a meaningful name,
864              make the pane a top-level menu item
865              with its items as a submenu beneath it.  */
866           if (!keymaps && strcmp (pane_string, ""))
867             {
868               wv = make_widget_value (pane_string, NULL, true, Qnil);
869               if (save_wv)
870                 save_wv->next = wv;
871               else
872                 first_wv->contents = wv;
873               if (keymaps && !NILP (prefix))
874                 wv->name++;
875               wv->button_type = BUTTON_TYPE_NONE;
876               save_wv = wv;
877               prev_wv = 0;
878             }
879           else if (first_pane)
880             {
881               save_wv = wv;
882               prev_wv = 0;
883             }
884           first_pane = 0;
885           i += MENU_ITEMS_PANE_LENGTH;
886         }
887       else
888         {
889           /* Create a new item within current pane.  */
890           Lisp_Object item_name, enable, descrip, def, type, selected, help;
891           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
892           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
893           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
894           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
895           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
896           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
897           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
899 #ifndef HAVE_MULTILINGUAL_MENU
900           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
901             {
902               item_name = ENCODE_MENU_STRING (item_name);
903               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
904             }
906           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
907             {
908               descrip = ENCODE_MENU_STRING (descrip);
909               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
910             }
911 #endif /* not HAVE_MULTILINGUAL_MENU */
913           wv = make_widget_value (SSDATA (item_name), NULL, !NILP (enable),
914                                   STRINGP (help) ? help : Qnil);
915           if (prev_wv)
916             prev_wv->next = wv;
917           else
918             save_wv->contents = wv;
919           if (!NILP (descrip))
920             wv->key = SSDATA (descrip);
921           /* If this item has a null value,
922              make the call_data null so that it won't display a box
923              when the mouse is on it.  */
924           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
926           if (NILP (type))
927             wv->button_type = BUTTON_TYPE_NONE;
928           else if (EQ (type, QCtoggle))
929             wv->button_type = BUTTON_TYPE_TOGGLE;
930           else if (EQ (type, QCradio))
931             wv->button_type = BUTTON_TYPE_RADIO;
932           else
933             emacs_abort ();
935           wv->selected = !NILP (selected);
937           prev_wv = wv;
939           i += MENU_ITEMS_ITEM_LENGTH;
940         }
941     }
942   }
943 #endif
945   if (!NILP (title))
946     {
947       widget_value *wv_title;
948       widget_value *wv_sep = make_widget_value ("--", NULL, false, Qnil);
950       /* Maybe replace this separator with a bitmap or owner-draw item
951          so that it looks better.  Having two separators looks odd.  */
952       wv_sep->next = first_wv->contents;
954 #ifndef HAVE_MULTILINGUAL_MENU
955       if (STRING_MULTIBYTE (title))
956         title = ENCODE_MENU_STRING (title);
957 #endif
958       wv_title = make_widget_value (SSDATA (title), NULL, false, Qnil);
959       wv_title->button_type = BUTTON_TYPE_NONE;
960       wv_title->next = wv_sep;
961       first_wv->contents = wv_title;
962     }
964   pmenu = [[EmacsMenu alloc] initWithTitle:
965                                [NSString stringWithUTF8String: SSDATA (title)]];
966   [pmenu fillWithWidgetValue: first_wv->contents];
967   free_menubar_widget_value_tree (first_wv);
968   unbind_to (specpdl_count, Qnil);
970   popup_activated_flag = 1;
971   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
972   popup_activated_flag = 0;
973   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
975   unblock_input ();
976   return tem;
980 /* ==========================================================================
982     Toolbar: externally-called functions
984    ========================================================================== */
986 void
987 free_frame_tool_bar (struct frame *f)
988 /* --------------------------------------------------------------------------
989     Under NS we just hide the toolbar until it might be needed again.
990    -------------------------------------------------------------------------- */
992   EmacsView *view = FRAME_NS_VIEW (f);
994   NSTRACE ("free_frame_tool_bar");
996   block_input ();
997   view->wait_for_tool_bar = NO;
999   FRAME_TOOLBAR_HEIGHT (f) = 0;
1001   /* Note: This trigger an animation, which calls windowDidResize
1002      repeatedly. */
1003   f->output_data.ns->in_animation = 1;
1004   [[view toolbar] setVisible: NO];
1005   f->output_data.ns->in_animation = 0;
1007   unblock_input ();
1010 void
1011 update_frame_tool_bar (struct frame *f)
1012 /* --------------------------------------------------------------------------
1013     Update toolbar contents
1014    -------------------------------------------------------------------------- */
1016   int i, k = 0;
1017   EmacsView *view = FRAME_NS_VIEW (f);
1018   NSWindow *window = [view window];
1019   EmacsToolbar *toolbar = [view toolbar];
1020   int oldh;
1022   NSTRACE ("update_frame_tool_bar");
1024   if (view == nil || toolbar == nil) return;
1025   block_input ();
1027   oldh = FRAME_TOOLBAR_HEIGHT (f);
1029 #ifdef NS_IMPL_COCOA
1030   [toolbar clearActive];
1031 #else
1032   [toolbar clearAll];
1033 #endif
1035   /* update EmacsToolbar as in GtkUtils, build items list */
1036   for (i = 0; i < f->n_tool_bar_items; ++i)
1037     {
1038 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1039                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1041       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1042       int idx;
1043       ptrdiff_t img_id;
1044       struct image *img;
1045       Lisp_Object image;
1046       Lisp_Object helpObj;
1047       const char *helpText;
1049       /* Check if this is a separator.  */
1050       if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1051         {
1052           /* Skip separators.  Newer OSX don't show them, and on GNUstep they
1053              are wide as a button, thus overflowing the toolbar most of
1054              the time.  */
1055           continue;
1056         }
1058       /* If image is a vector, choose the image according to the
1059          button state.  */
1060       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1061       if (VECTORP (image))
1062         {
1063           /* NS toolbar auto-computes disabled and selected images */
1064           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1065           eassert (ASIZE (image) >= idx);
1066           image = AREF (image, idx);
1067         }
1068       else
1069         {
1070           idx = -1;
1071         }
1072       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1073       if (NILP (helpObj))
1074         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1075       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1077       /* Ignore invalid image specifications.  */
1078       if (!valid_image_p (image))
1079         {
1080           /* Don't log anything, GNUS makes invalid images all the time.  */
1081           continue;
1082         }
1084       img_id = lookup_image (f, image);
1085       img = IMAGE_FROM_ID (f, img_id);
1086       prepare_image_for_display (f, img);
1088       if (img->load_failed_p || img->pixmap == nil)
1089         {
1090           NSLog (@"Could not prepare toolbar image for display.");
1091           continue;
1092         }
1094       [toolbar addDisplayItemWithImage: img->pixmap
1095                                    idx: k++
1096                                    tag: i
1097                               helpText: helpText
1098                                enabled: enabled_p];
1099 #undef TOOLPROP
1100     }
1102   if (![toolbar isVisible])
1103     {
1104       f->output_data.ns->in_animation = 1;
1105       [toolbar setVisible: YES];
1106       f->output_data.ns->in_animation = 0;
1107     }
1109 #ifdef NS_IMPL_COCOA
1110   if ([toolbar changed])
1111     {
1112       /* inform app that toolbar has changed */
1113       NSDictionary *dict = [toolbar configurationDictionary];
1114       NSMutableDictionary *newDict = [dict mutableCopy];
1115       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1116       id key;
1117       while ((key = [keys nextObject]) != nil)
1118         {
1119           NSObject *val = [dict objectForKey: key];
1120           if ([val isKindOfClass: [NSArray class]])
1121             {
1122               [newDict setObject:
1123                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1124                           forKey: key];
1125               break;
1126             }
1127         }
1128       [toolbar setConfigurationFromDictionary: newDict];
1129       [newDict release];
1130     }
1131 #endif
1133   FRAME_TOOLBAR_HEIGHT (f) =
1134     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1135     - FRAME_NS_TITLEBAR_HEIGHT (f);
1136   if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1137     FRAME_TOOLBAR_HEIGHT (f) = 0;
1139   if (oldh != FRAME_TOOLBAR_HEIGHT (f))
1140     [view updateFrameSize:YES];
1141   if (view->wait_for_tool_bar && FRAME_TOOLBAR_HEIGHT (f) > 0)
1142     {
1143       view->wait_for_tool_bar = NO;
1144       [view setNeedsDisplay: YES];
1145     }
1147   unblock_input ();
1151 /* ==========================================================================
1153     Toolbar: class implementation
1155    ========================================================================== */
1157 @implementation EmacsToolbar
1159 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1161   NSTRACE ("[EmacsToolbar initForView: withIdentifier:]");
1163   self = [super initWithIdentifier: identifier];
1164   emacsView = view;
1165   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1166   [self setSizeMode: NSToolbarSizeModeSmall];
1167   [self setDelegate: self];
1168   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1169   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1170   prevIdentifiers = nil;
1171   prevEnablement = enablement = 0L;
1172   return self;
1175 - (void)dealloc
1177   NSTRACE ("[EmacsToolbar dealloc]");
1179   [prevIdentifiers release];
1180   [activeIdentifiers release];
1181   [identifierToItem release];
1182   [super dealloc];
1185 - (void) clearActive
1187   NSTRACE ("[EmacsToolbar clearActive]");
1189   [prevIdentifiers release];
1190   prevIdentifiers = [activeIdentifiers copy];
1191   [activeIdentifiers removeAllObjects];
1192   prevEnablement = enablement;
1193   enablement = 0L;
1196 - (void) clearAll
1198   NSTRACE ("[EmacsToolbar clearAll]");
1200   [self clearActive];
1201   while ([[self items] count] > 0)
1202     [self removeItemAtIndex: 0];
1205 - (BOOL) changed
1207   NSTRACE ("[EmacsToolbar changed]");
1209   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1210     enablement == prevEnablement ? NO : YES;
1213 - (void) addDisplayItemWithImage: (EmacsImage *)img
1214                              idx: (int)idx
1215                              tag: (int)tag
1216                         helpText: (const char *)help
1217                          enabled: (BOOL)enabled
1219   NSTRACE ("[EmacsToolbar addDisplayItemWithImage: ...]");
1221   /* 1) come up w/identifier */
1222   NSString *identifier
1223     = [NSString stringWithFormat: @"%lu", (unsigned long)[img hash]];
1224   [activeIdentifiers addObject: identifier];
1226   /* 2) create / reuse item */
1227   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1228   if (item == nil)
1229     {
1230       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1231                autorelease];
1232       [item setImage: img];
1233       [item setToolTip: [NSString stringWithUTF8String: help]];
1234       [item setTarget: emacsView];
1235       [item setAction: @selector (toolbarClicked:)];
1236       [identifierToItem setObject: item forKey: identifier];
1237     }
1239 #ifdef NS_IMPL_GNUSTEP
1240   [self insertItemWithItemIdentifier: identifier atIndex: idx];
1241 #endif
1243   [item setTag: tag];
1244   [item setEnabled: enabled];
1246   /* 3) update state */
1247   enablement = (enablement << 1) | (enabled == YES);
1250 /* This overrides super's implementation, which automatically sets
1251    all items to enabled state (for some reason). */
1252 - (void)validateVisibleItems
1254   NSTRACE ("[EmacsToolbar validateVisibleItems]");
1258 /* delegate methods */
1260 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1261       itemForItemIdentifier: (NSString *)itemIdentifier
1262   willBeInsertedIntoToolbar: (BOOL)flag
1264   NSTRACE ("[EmacsToolbar toolbar: ...]");
1266   /* look up NSToolbarItem by identifier and return... */
1267   return [identifierToItem objectForKey: itemIdentifier];
1270 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1272   NSTRACE ("[EmacsToolbar toolbarDefaultItemIdentifiers:]");
1274   /* return entire set.. */
1275   return activeIdentifiers;
1278 /* for configuration palette (not yet supported) */
1279 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1281   NSTRACE ("[EmacsToolbar toolbarAllowedItemIdentifiers:]");
1283   /* return entire set... */
1284   return activeIdentifiers;
1285   //return [identifierToItem allKeys];
1288 - (void)setVisible:(BOOL)shown
1290   NSTRACE ("[EmacsToolbar setVisible:%d]", shown);
1292   [super setVisible:shown];
1296 /* optional and unneeded */
1297 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1298 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1299 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1301 @end  /* EmacsToolbar */
1305 /* ==========================================================================
1307     Tooltip: class implementation
1309    ========================================================================== */
1311 /* Needed because NeXTstep does not provide enough control over tooltip
1312    display. */
1313 @implementation EmacsTooltip
1315 - init
1317   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1318                                             blue: 0.792 alpha: 0.95];
1319   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1320   NSFont *sfont = [font screenFont];
1321   int height = [sfont ascender] - [sfont descender];
1322 /*[font boundingRectForFont].size.height; */
1323   NSRect r = NSMakeRect (0, 0, 100, height+6);
1325   textField = [[NSTextField alloc] initWithFrame: r];
1326   [textField setFont: font];
1327   [textField setBackgroundColor: col];
1329   [textField setEditable: NO];
1330   [textField setSelectable: NO];
1331   [textField setBordered: NO];
1332   [textField setBezeled: NO];
1333   [textField setDrawsBackground: YES];
1335   win = [[NSWindow alloc]
1336             initWithContentRect: [textField frame]
1337                       styleMask: 0
1338                         backing: NSBackingStoreBuffered
1339                           defer: YES];
1340   [win setHasShadow: YES];
1341   [win setReleasedWhenClosed: NO];
1342   [win setDelegate: self];
1343   [[win contentView] addSubview: textField];
1344 /*  [win setBackgroundColor: col]; */
1345   [win setOpaque: NO];
1347   return self;
1350 - (void) dealloc
1352   [win close];
1353   [win release];
1354   [textField release];
1355   [super dealloc];
1358 - (void) setText: (char *)text
1360   NSString *str = [NSString stringWithUTF8String: text];
1361   NSRect r  = [textField frame];
1362   NSSize tooltipDims;
1364   [textField setStringValue: str];
1365   tooltipDims = [[textField cell] cellSize];
1367   r.size.width = tooltipDims.width;
1368   r.size.height = tooltipDims.height;
1369   [textField setFrame: r];
1372 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1374   NSRect wr = [win frame];
1376   wr.origin = NSMakePoint (x, y);
1377   wr.size = [textField frame].size;
1379   [win setFrame: wr display: YES];
1380   [win setLevel: NSPopUpMenuWindowLevel];
1381   [win orderFront: self];
1382   [win display];
1383   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1384                                          selector: @selector (hide)
1385                                          userInfo: nil repeats: NO];
1386   [timer retain];
1389 - (void) hide
1391   [win close];
1392   if (timer != nil)
1393     {
1394       if ([timer isValid])
1395         [timer invalidate];
1396       [timer release];
1397       timer = nil;
1398     }
1401 - (BOOL) isActive
1403   return timer != nil;
1406 - (NSRect) frame
1408   return [textField frame];
1411 @end  /* EmacsTooltip */
1415 /* ==========================================================================
1417     Popup Dialog: implementing functions
1419    ========================================================================== */
1421 static void
1422 pop_down_menu (void *arg)
1424   EmacsDialogPanel *panel = arg;
1426   if (popup_activated_flag)
1427     {
1428       block_input ();
1429       popup_activated_flag = 0;
1430       [panel close];
1431       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1432       unblock_input ();
1433     }
1437 Lisp_Object
1438 ns_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
1440   id dialog;
1441   Lisp_Object tem, title;
1442   NSPoint p;
1443   BOOL isQ;
1445   NSTRACE ("ns_popup_dialog");
1447   isQ = NILP (header);
1449   check_window_system (f);
1451   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1452   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1454   title = Fcar (contents);
1455   CHECK_STRING (title);
1457   if (NILP (Fcar (Fcdr (contents))))
1458     /* No buttons specified, add an "Ok" button so users can pop down
1459        the dialog.  */
1460     contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1462   block_input ();
1463   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1464                                            isQuestion: isQ];
1466   {
1467     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1469     record_unwind_protect_ptr (pop_down_menu, dialog);
1470     popup_activated_flag = 1;
1471     tem = [dialog runDialogAt: p];
1472     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1473   }
1475   unblock_input ();
1477   return tem;
1481 /* ==========================================================================
1483     Popup Dialog: class implementation
1485    ========================================================================== */
1487 @interface FlippedView : NSView
1490 @end
1492 @implementation FlippedView
1493 - (BOOL)isFlipped
1495   return YES;
1497 @end
1499 @implementation EmacsDialogPanel
1501 #define SPACER          8.0
1502 #define ICONSIZE        64.0
1503 #define TEXTHEIGHT      20.0
1504 #define MINCELLWIDTH    90.0
1506 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1507               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1509   NSSize spacing = {SPACER, SPACER};
1510   NSRect area;
1511   id cell;
1512   NSImageView *imgView;
1513   FlippedView *contentView;
1514   NSImage *img;
1516   dialog_return   = Qundefined;
1517   button_values   = NULL;
1518   area.origin.x   = 3*SPACER;
1519   area.origin.y   = 2*SPACER;
1520   area.size.width = ICONSIZE;
1521   area.size.height= ICONSIZE;
1522   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1523 #ifdef NS_IMPL_COCOA
1524 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
1525   [img setScalesWhenResized: YES];
1526 #endif
1527 #endif
1528   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1529   imgView = [[NSImageView alloc] initWithFrame: area];
1530   [imgView setImage: img];
1531   [imgView setEditable: NO];
1532   [img autorelease];
1533   [imgView autorelease];
1535   aStyle = NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSUtilityWindowMask;
1536   flag = YES;
1537   rows = 0;
1538   cols = 1;
1539   [super initWithContentRect: contentRect styleMask: aStyle
1540                      backing: backingType defer: flag];
1541   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1542   [contentView autorelease];
1544   [self setContentView: contentView];
1546   [[self contentView] setAutoresizesSubviews: YES];
1548   [[self contentView] addSubview: imgView];
1549   [self setTitle: @""];
1551   area.origin.x   += ICONSIZE+2*SPACER;
1552 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1553   area.size.width = 400;
1554   area.size.height= TEXTHEIGHT;
1555   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1556   [[self contentView] addSubview: command];
1557   [command setStringValue: ns_app_name];
1558   [command setDrawsBackground: NO];
1559   [command setBezeled: NO];
1560   [command setSelectable: NO];
1561   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1563 /*  area.origin.x   = ICONSIZE+2*SPACER;
1564   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1565   area.size.width = 400;
1566   area.size.height= 2;
1567   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1568   [[self contentView] addSubview: tem];
1569   [tem setTitlePosition: NSNoTitle];
1570   [tem setAutoresizingMask: NSViewWidthSizable];*/
1572 /*  area.origin.x = ICONSIZE+2*SPACER; */
1573   area.origin.y += TEXTHEIGHT+SPACER;
1574   area.size.width = 400;
1575   area.size.height= TEXTHEIGHT;
1576   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1577   [[self contentView] addSubview: title];
1578   [title setDrawsBackground: NO];
1579   [title setBezeled: NO];
1580   [title setSelectable: NO];
1581   [title setFont: [NSFont systemFontOfSize: 11.0]];
1583   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1584   [cell setBordered: NO];
1585   [cell setEnabled: NO];
1586   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1587   [cell setBezelStyle: NSRoundedBezelStyle];
1589   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1590                                       mode: NSHighlightModeMatrix
1591                                  prototype: cell
1592                               numberOfRows: 0
1593                            numberOfColumns: 1];
1594   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1595                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1596   [matrix setIntercellSpacing: spacing];
1597   [matrix autorelease];
1599   [[self contentView] addSubview: matrix];
1600   [self setOneShot: YES];
1601   [self setReleasedWhenClosed: YES];
1602   [self setHidesOnDeactivate: YES];
1603   return self;
1607 - (BOOL)windowShouldClose: (id)sender
1609   window_closed = YES;
1610   [NSApp stop:self];
1611   return NO;
1614 - (void)dealloc
1616   xfree (button_values);
1617   [super dealloc];
1620 - (void)process_dialog: (Lisp_Object) list
1622   Lisp_Object item, lst = list;
1623   int row = 0;
1624   int buttons = 0, btnnr = 0;
1626   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1627     {
1628       item = XCAR (list);
1629       if (XTYPE (item) == Lisp_Cons)
1630         ++buttons;
1631     }
1633   if (buttons > 0)
1634     button_values = xmalloc (buttons * sizeof *button_values);
1636   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1637     {
1638       item = XCAR (list);
1639       if (XTYPE (item) == Lisp_String)
1640         {
1641           [self addString: SSDATA (item) row: row++];
1642         }
1643       else if (XTYPE (item) == Lisp_Cons)
1644         {
1645           button_values[btnnr] = XCDR (item);
1646           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1647           ++btnnr;
1648         }
1649       else if (NILP (item))
1650         {
1651           [self addSplit];
1652           row = 0;
1653         }
1654     }
1658 - (void)addButton: (char *)str value: (int)tag row: (int)row
1660   id cell;
1662   if (row >= rows)
1663     {
1664       [matrix addRow];
1665       rows++;
1666     }
1667   cell = [matrix cellAtRow: row column: cols-1];
1668   [cell setTarget: self];
1669   [cell setAction: @selector (clicked: )];
1670   [cell setTitle: [NSString stringWithUTF8String: str]];
1671   [cell setTag: tag];
1672   [cell setBordered: YES];
1673   [cell setEnabled: YES];
1677 - (void)addString: (char *)str row: (int)row
1679   id cell;
1681   if (row >= rows)
1682     {
1683       [matrix addRow];
1684       rows++;
1685     }
1686   cell = [matrix cellAtRow: row column: cols-1];
1687   [cell setTitle: [NSString stringWithUTF8String: str]];
1688   [cell setBordered: YES];
1689   [cell setEnabled: NO];
1693 - (void)addSplit
1695   [matrix addColumn];
1696   cols++;
1700 - (void)clicked: sender
1702   NSArray *sellist = nil;
1703   EMACS_INT seltag;
1705   sellist = [sender selectedCells];
1706   if ([sellist count] < 1)
1707     return;
1709   seltag = [[sellist objectAtIndex: 0] tag];
1710   dialog_return = button_values[seltag];
1711   [NSApp stop:self];
1715 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1717   Lisp_Object head;
1718   [super init];
1720   if (XTYPE (contents) == Lisp_Cons)
1721     {
1722       head = Fcar (contents);
1723       [self process_dialog: Fcdr (contents)];
1724     }
1725   else
1726     head = contents;
1728   if (XTYPE (head) == Lisp_String)
1729       [title setStringValue:
1730                  [NSString stringWithUTF8String: SSDATA (head)]];
1731   else if (isQ == YES)
1732       [title setStringValue: @"Question"];
1733   else
1734       [title setStringValue: @"Information"];
1736   {
1737     int i;
1738     NSRect r, s, t;
1740     if (cols == 1 && rows > 1)  /* Never told where to split */
1741       {
1742         [matrix addColumn];
1743         for (i = 0; i < rows/2; i++)
1744           {
1745             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1746                       atRow: i column: 1];
1747             [matrix removeRow: (rows+1)/2];
1748           }
1749       }
1751     [matrix sizeToFit];
1752     {
1753       NSSize csize = [matrix cellSize];
1754       if (csize.width < MINCELLWIDTH)
1755         {
1756           csize.width = MINCELLWIDTH;
1757           [matrix setCellSize: csize];
1758           [matrix sizeToCells];
1759         }
1760     }
1762     [title sizeToFit];
1763     [command sizeToFit];
1765     t = [matrix frame];
1766     r = [title frame];
1767     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1768       {
1769         t.origin.x   = r.origin.x;
1770         t.size.width = r.size.width;
1771       }
1772     r = [command frame];
1773     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1774       {
1775         t.origin.x   = r.origin.x;
1776         t.size.width = r.size.width;
1777       }
1779     r = [self frame];
1780     s = [(NSView *)[self contentView] frame];
1781     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1782     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1783     [self setFrame: r display: NO];
1784   }
1786   return self;
1791 - (void)timeout_handler: (NSTimer *)timedEntry
1793   NSEvent *nxev = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
1794                             location: NSMakePoint (0, 0)
1795                        modifierFlags: 0
1796                            timestamp: 0
1797                         windowNumber: [[NSApp mainWindow] windowNumber]
1798                              context: [NSApp context]
1799                              subtype: 0
1800                                data1: 0
1801                                data2: 0];
1803   timer_fired = YES;
1804   /* We use sto because stopModal/abortModal out of the main loop does not
1805      seem to work in 10.6.  But as we use stop we must send a real event so
1806      the stop is seen and acted upon.  */
1807   [NSApp stop:self];
1808   [NSApp postEvent: nxev atStart: NO];
1811 - (Lisp_Object)runDialogAt: (NSPoint)p
1813   Lisp_Object ret = Qundefined;
1815   while (popup_activated_flag)
1816     {
1817       NSTimer *tmo = nil;
1818       struct timespec next_time = timer_check ();
1820       if (timespec_valid_p (next_time))
1821         {
1822           double time = timespectod (next_time);
1823           tmo = [NSTimer timerWithTimeInterval: time
1824                                         target: self
1825                                       selector: @selector (timeout_handler:)
1826                                       userInfo: 0
1827                                        repeats: NO];
1828           [[NSRunLoop currentRunLoop] addTimer: tmo
1829                                        forMode: NSModalPanelRunLoopMode];
1830         }
1831       timer_fired = NO;
1832       dialog_return = Qundefined;
1833       [NSApp runModalForWindow: self];
1834       ret = dialog_return;
1835       if (! timer_fired)
1836         {
1837           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1838           break;
1839         }
1840     }
1842   if (EQ (ret, Qundefined) && window_closed)
1843     /* Make close button pressed equivalent to C-g.  */
1844     quit ();
1846   return ret;
1849 @end
1852 /* ==========================================================================
1854     Lisp definitions
1856    ========================================================================== */
1858 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1859        doc: /* Cause the NS menu to be re-calculated.  */)
1860      (void)
1862   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1863   return Qnil;
1867 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1868        doc: /* Return t if a menu or popup dialog is active.  */)
1869      (void)
1871   return popup_activated () ? Qt : Qnil;
1874 /* ==========================================================================
1876     Lisp interface declaration
1878    ========================================================================== */
1880 void
1881 syms_of_nsmenu (void)
1883 #ifndef NS_IMPL_COCOA
1884   /* Don't know how to keep track of this in Next/Open/GNUstep.  Always
1885      update menus there.  */
1886   trackingMenu = 1;
1887 #endif
1888   defsubr (&Sns_reset_menu);
1889   defsubr (&Smenu_or_popup_active_p);
1891   DEFSYM (Qdebug_on_next_call, "debug-on-next-call");