‘signal’ no longer returns
[emacs.git] / src / nsmenu.m
blobd1f4b020bb07fd5429b0bd9676d13b8c69da4289
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 = NSCommandKeyMask;
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 setMenuChangedMessagesEnabled: NO];
696   [self clear];
698   /* add new contents */
699   for (; wv != NULL; wv = wv->next)
700     {
701       NSMenuItem *item = [self addItemWithWidgetValue: wv];
703       if (wv->contents)
704         {
705           EmacsMenu *submenu;
707           if (f)
708             submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f];
709           else
710             submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
712           [self setSubmenu: submenu forItem: item];
713           [submenu fillWithWidgetValue: wv->contents];
714           [submenu release];
715           [item setAction: (SEL)nil];
716         }
717     }
719   [self setMenuChangedMessagesEnabled: YES];
720 #ifdef NS_IMPL_GNUSTEP
721   if ([[self window] isVisible])
722     [self sizeToFit];
723 #endif
727 /* adds an empty submenu and returns it */
728 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
730   NSString *titleStr = [NSString stringWithUTF8String: title];
731   NSMenuItem *item = [self addItemWithTitle: titleStr
732                                      action: (SEL)nil /*@selector (menuDown:) */
733                               keyEquivalent: @""];
734   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
735   [self setSubmenu: submenu forItem: item];
736   [submenu release];
737   return submenu;
740 /* run a menu in popup mode */
741 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
742                  keymaps: (bool)keymaps
744   EmacsView *view = FRAME_NS_VIEW (f);
745   NSEvent *e, *event;
746   long retVal;
748 /*   p = [view convertPoint:p fromView: nil]; */
749   p.y = NSHeight ([view frame]) - p.y;
750   e = [[view window] currentEvent];
751    event = [NSEvent mouseEventWithType: NSRightMouseDown
752                               location: p
753                          modifierFlags: 0
754                              timestamp: [e timestamp]
755                           windowNumber: [[view window] windowNumber]
756                                context: [e context]
757                            eventNumber: 0/*[e eventNumber] */
758                             clickCount: 1
759                               pressure: 0];
761   context_menu_value = -1;
762   [NSMenu popUpContextMenu: self withEvent: event forView: view];
763   retVal = context_menu_value;
764   context_menu_value = 0;
765   return retVal > 0
766       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
767       : Qnil;
770 @end  /* EmacsMenu */
774 /* ==========================================================================
776     Context Menu: implementing functions
778    ========================================================================== */
780 Lisp_Object
781 ns_menu_show (struct frame *f, int x, int y, int menuflags,
782               Lisp_Object title, const char **error)
784   EmacsMenu *pmenu;
785   NSPoint p;
786   Lisp_Object tem;
787   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
788   widget_value *wv, *first_wv = 0;
789   bool keymaps = (menuflags & MENU_KEYMAPS);
791   NSTRACE ("ns_menu_show");
793   block_input ();
795   p.x = x; p.y = y;
797   /* now parse stage 2 as in ns_update_menubar */
798   wv = make_widget_value ("contextmenu", NULL, true, Qnil);
799   wv->button_type = BUTTON_TYPE_NONE;
800   first_wv = wv;
802 #if 0
803   /* FIXME: a couple of one-line differences prevent reuse */
804   wv = digest_single_submenu (0, menu_items_used, 0);
805 #else
806   {
807   widget_value *save_wv = 0, *prev_wv = 0;
808   widget_value **submenu_stack
809     = alloca (menu_items_used * sizeof *submenu_stack);
810 /*   Lisp_Object *subprefix_stack
811        = alloca (menu_items_used * sizeof *subprefix_stack); */
812   int submenu_depth = 0;
813   int first_pane = 1;
814   int i;
816   /* Loop over all panes and items, filling in the tree.  */
817   i = 0;
818   while (i < menu_items_used)
819     {
820       if (EQ (AREF (menu_items, i), Qnil))
821         {
822           submenu_stack[submenu_depth++] = save_wv;
823           save_wv = prev_wv;
824           prev_wv = 0;
825           first_pane = 1;
826           i++;
827         }
828       else if (EQ (AREF (menu_items, i), Qlambda))
829         {
830           prev_wv = save_wv;
831           save_wv = submenu_stack[--submenu_depth];
832           first_pane = 0;
833           i++;
834         }
835       else if (EQ (AREF (menu_items, i), Qt)
836                && submenu_depth != 0)
837         i += MENU_ITEMS_PANE_LENGTH;
838       /* Ignore a nil in the item list.
839          It's meaningful only for dialog boxes.  */
840       else if (EQ (AREF (menu_items, i), Qquote))
841         i += 1;
842       else if (EQ (AREF (menu_items, i), Qt))
843         {
844           /* Create a new pane.  */
845           Lisp_Object pane_name, prefix;
846           const char *pane_string;
848           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
849           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
851 #ifndef HAVE_MULTILINGUAL_MENU
852           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
853             {
854               pane_name = ENCODE_MENU_STRING (pane_name);
855               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
856             }
857 #endif
858           pane_string = (NILP (pane_name)
859                          ? "" : SSDATA (pane_name));
860           /* If there is just one top-level pane, put all its items directly
861              under the top-level menu.  */
862           if (menu_items_n_panes == 1)
863             pane_string = "";
865           /* If the pane has a meaningful name,
866              make the pane a top-level menu item
867              with its items as a submenu beneath it.  */
868           if (!keymaps && strcmp (pane_string, ""))
869             {
870               wv = make_widget_value (pane_string, NULL, true, Qnil);
871               if (save_wv)
872                 save_wv->next = wv;
873               else
874                 first_wv->contents = wv;
875               if (keymaps && !NILP (prefix))
876                 wv->name++;
877               wv->button_type = BUTTON_TYPE_NONE;
878               save_wv = wv;
879               prev_wv = 0;
880             }
881           else if (first_pane)
882             {
883               save_wv = wv;
884               prev_wv = 0;
885             }
886           first_pane = 0;
887           i += MENU_ITEMS_PANE_LENGTH;
888         }
889       else
890         {
891           /* Create a new item within current pane.  */
892           Lisp_Object item_name, enable, descrip, def, type, selected, help;
893           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
894           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
895           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
896           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
897           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
898           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
899           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
901 #ifndef HAVE_MULTILINGUAL_MENU
902           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
903             {
904               item_name = ENCODE_MENU_STRING (item_name);
905               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
906             }
908           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
909             {
910               descrip = ENCODE_MENU_STRING (descrip);
911               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
912             }
913 #endif /* not HAVE_MULTILINGUAL_MENU */
915           wv = make_widget_value (SSDATA (item_name), NULL, !NILP (enable),
916                                   STRINGP (help) ? help : Qnil);
917           if (prev_wv)
918             prev_wv->next = wv;
919           else
920             save_wv->contents = wv;
921           if (!NILP (descrip))
922             wv->key = SSDATA (descrip);
923           /* If this item has a null value,
924              make the call_data null so that it won't display a box
925              when the mouse is on it.  */
926           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
928           if (NILP (type))
929             wv->button_type = BUTTON_TYPE_NONE;
930           else if (EQ (type, QCtoggle))
931             wv->button_type = BUTTON_TYPE_TOGGLE;
932           else if (EQ (type, QCradio))
933             wv->button_type = BUTTON_TYPE_RADIO;
934           else
935             emacs_abort ();
937           wv->selected = !NILP (selected);
939           prev_wv = wv;
941           i += MENU_ITEMS_ITEM_LENGTH;
942         }
943     }
944   }
945 #endif
947   if (!NILP (title))
948     {
949       widget_value *wv_title;
950       widget_value *wv_sep = make_widget_value ("--", NULL, false, Qnil);
952       /* Maybe replace this separator with a bitmap or owner-draw item
953          so that it looks better.  Having two separators looks odd.  */
954       wv_sep->next = first_wv->contents;
956 #ifndef HAVE_MULTILINGUAL_MENU
957       if (STRING_MULTIBYTE (title))
958         title = ENCODE_MENU_STRING (title);
959 #endif
960       wv_title = make_widget_value (SSDATA (title), NULL, false, Qnil);
961       wv_title->button_type = BUTTON_TYPE_NONE;
962       wv_title->next = wv_sep;
963       first_wv->contents = wv_title;
964     }
966   pmenu = [[EmacsMenu alloc] initWithTitle:
967                                [NSString stringWithUTF8String: SSDATA (title)]];
968   [pmenu fillWithWidgetValue: first_wv->contents];
969   free_menubar_widget_value_tree (first_wv);
970   unbind_to (specpdl_count, Qnil);
972   popup_activated_flag = 1;
973   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
974   popup_activated_flag = 0;
975   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
977   unblock_input ();
978   return tem;
982 /* ==========================================================================
984     Toolbar: externally-called functions
986    ========================================================================== */
988 void
989 free_frame_tool_bar (struct frame *f)
990 /* --------------------------------------------------------------------------
991     Under NS we just hide the toolbar until it might be needed again.
992    -------------------------------------------------------------------------- */
994   EmacsView *view = FRAME_NS_VIEW (f);
996   NSTRACE ("free_frame_tool_bar");
998   block_input ();
999   view->wait_for_tool_bar = NO;
1001   FRAME_TOOLBAR_HEIGHT (f) = 0;
1003   /* Note: This trigger an animation, which calls windowDidResize
1004      repeatedly. */
1005   f->output_data.ns->in_animation = 1;
1006   [[view toolbar] setVisible: NO];
1007   f->output_data.ns->in_animation = 0;
1009   unblock_input ();
1012 void
1013 update_frame_tool_bar (struct frame *f)
1014 /* --------------------------------------------------------------------------
1015     Update toolbar contents
1016    -------------------------------------------------------------------------- */
1018   int i, k = 0;
1019   EmacsView *view = FRAME_NS_VIEW (f);
1020   NSWindow *window = [view window];
1021   EmacsToolbar *toolbar = [view toolbar];
1022   int oldh;
1024   NSTRACE ("update_frame_tool_bar");
1026   if (view == nil || toolbar == nil) return;
1027   block_input ();
1029   oldh = FRAME_TOOLBAR_HEIGHT (f);
1031 #ifdef NS_IMPL_COCOA
1032   [toolbar clearActive];
1033 #else
1034   [toolbar clearAll];
1035 #endif
1037   /* update EmacsToolbar as in GtkUtils, build items list */
1038   for (i = 0; i < f->n_tool_bar_items; ++i)
1039     {
1040 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1041                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1043       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1044       int idx;
1045       ptrdiff_t img_id;
1046       struct image *img;
1047       Lisp_Object image;
1048       Lisp_Object helpObj;
1049       const char *helpText;
1051       /* Check if this is a separator.  */
1052       if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1053         {
1054           /* Skip separators.  Newer OSX don't show them, and on GNUstep they
1055              are wide as a button, thus overflowing the toolbar most of
1056              the time.  */
1057           continue;
1058         }
1060       /* If image is a vector, choose the image according to the
1061          button state.  */
1062       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1063       if (VECTORP (image))
1064         {
1065           /* NS toolbar auto-computes disabled and selected images */
1066           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1067           eassert (ASIZE (image) >= idx);
1068           image = AREF (image, idx);
1069         }
1070       else
1071         {
1072           idx = -1;
1073         }
1074       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1075       if (NILP (helpObj))
1076         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1077       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1079       /* Ignore invalid image specifications.  */
1080       if (!valid_image_p (image))
1081         {
1082           /* Don't log anything, GNUS makes invalid images all the time.  */
1083           continue;
1084         }
1086       img_id = lookup_image (f, image);
1087       img = IMAGE_FROM_ID (f, img_id);
1088       prepare_image_for_display (f, img);
1090       if (img->load_failed_p || img->pixmap == nil)
1091         {
1092           NSLog (@"Could not prepare toolbar image for display.");
1093           continue;
1094         }
1096       [toolbar addDisplayItemWithImage: img->pixmap
1097                                    idx: k++
1098                                    tag: i
1099                               helpText: helpText
1100                                enabled: enabled_p];
1101 #undef TOOLPROP
1102     }
1104   if (![toolbar isVisible])
1105     {
1106       f->output_data.ns->in_animation = 1;
1107       [toolbar setVisible: YES];
1108       f->output_data.ns->in_animation = 0;
1109     }
1111 #ifdef NS_IMPL_COCOA
1112   if ([toolbar changed])
1113     {
1114       /* inform app that toolbar has changed */
1115       NSDictionary *dict = [toolbar configurationDictionary];
1116       NSMutableDictionary *newDict = [dict mutableCopy];
1117       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1118       id key;
1119       while ((key = [keys nextObject]) != nil)
1120         {
1121           NSObject *val = [dict objectForKey: key];
1122           if ([val isKindOfClass: [NSArray class]])
1123             {
1124               [newDict setObject:
1125                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1126                           forKey: key];
1127               break;
1128             }
1129         }
1130       [toolbar setConfigurationFromDictionary: newDict];
1131       [newDict release];
1132     }
1133 #endif
1135   FRAME_TOOLBAR_HEIGHT (f) =
1136     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1137     - FRAME_NS_TITLEBAR_HEIGHT (f);
1138   if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1139     FRAME_TOOLBAR_HEIGHT (f) = 0;
1141   if (oldh != FRAME_TOOLBAR_HEIGHT (f))
1142     [view updateFrameSize:YES];
1143   if (view->wait_for_tool_bar && FRAME_TOOLBAR_HEIGHT (f) > 0)
1144     {
1145       view->wait_for_tool_bar = NO;
1146       [view setNeedsDisplay: YES];
1147     }
1149   unblock_input ();
1153 /* ==========================================================================
1155     Toolbar: class implementation
1157    ========================================================================== */
1159 @implementation EmacsToolbar
1161 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1163   NSTRACE ("[EmacsToolbar initForView: withIdentifier:]");
1165   self = [super initWithIdentifier: identifier];
1166   emacsView = view;
1167   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1168   [self setSizeMode: NSToolbarSizeModeSmall];
1169   [self setDelegate: self];
1170   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1171   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1172   prevIdentifiers = nil;
1173   prevEnablement = enablement = 0L;
1174   return self;
1177 - (void)dealloc
1179   NSTRACE ("[EmacsToolbar dealloc]");
1181   [prevIdentifiers release];
1182   [activeIdentifiers release];
1183   [identifierToItem release];
1184   [super dealloc];
1187 - (void) clearActive
1189   NSTRACE ("[EmacsToolbar clearActive]");
1191   [prevIdentifiers release];
1192   prevIdentifiers = [activeIdentifiers copy];
1193   [activeIdentifiers removeAllObjects];
1194   prevEnablement = enablement;
1195   enablement = 0L;
1198 - (void) clearAll
1200   NSTRACE ("[EmacsToolbar clearAll]");
1202   [self clearActive];
1203   while ([[self items] count] > 0)
1204     [self removeItemAtIndex: 0];
1207 - (BOOL) changed
1209   NSTRACE ("[EmacsToolbar changed]");
1211   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1212     enablement == prevEnablement ? NO : YES;
1215 - (void) addDisplayItemWithImage: (EmacsImage *)img
1216                              idx: (int)idx
1217                              tag: (int)tag
1218                         helpText: (const char *)help
1219                          enabled: (BOOL)enabled
1221   NSTRACE ("[EmacsToolbar addDisplayItemWithImage: ...]");
1223   /* 1) come up w/identifier */
1224   NSString *identifier
1225     = [NSString stringWithFormat: @"%lu", (unsigned long)[img hash]];
1226   [activeIdentifiers addObject: identifier];
1228   /* 2) create / reuse item */
1229   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1230   if (item == nil)
1231     {
1232       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1233                autorelease];
1234       [item setImage: img];
1235       [item setToolTip: [NSString stringWithUTF8String: help]];
1236       [item setTarget: emacsView];
1237       [item setAction: @selector (toolbarClicked:)];
1238       [identifierToItem setObject: item forKey: identifier];
1239     }
1241 #ifdef NS_IMPL_GNUSTEP
1242   [self insertItemWithItemIdentifier: identifier atIndex: idx];
1243 #endif
1245   [item setTag: tag];
1246   [item setEnabled: enabled];
1248   /* 3) update state */
1249   enablement = (enablement << 1) | (enabled == YES);
1252 /* This overrides super's implementation, which automatically sets
1253    all items to enabled state (for some reason). */
1254 - (void)validateVisibleItems
1256   NSTRACE ("[EmacsToolbar validateVisibleItems]");
1260 /* delegate methods */
1262 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1263       itemForItemIdentifier: (NSString *)itemIdentifier
1264   willBeInsertedIntoToolbar: (BOOL)flag
1266   NSTRACE ("[EmacsToolbar toolbar: ...]");
1268   /* look up NSToolbarItem by identifier and return... */
1269   return [identifierToItem objectForKey: itemIdentifier];
1272 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1274   NSTRACE ("[EmacsToolbar toolbarDefaultItemIdentifiers:]");
1276   /* return entire set.. */
1277   return activeIdentifiers;
1280 /* for configuration palette (not yet supported) */
1281 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1283   NSTRACE ("[EmacsToolbar toolbarAllowedItemIdentifiers:]");
1285   /* return entire set... */
1286   return activeIdentifiers;
1287   //return [identifierToItem allKeys];
1290 - (void)setVisible:(BOOL)shown
1292   NSTRACE ("[EmacsToolbar setVisible:%d]", shown);
1294   [super setVisible:shown];
1298 /* optional and unneeded */
1299 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1300 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1301 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1303 @end  /* EmacsToolbar */
1307 /* ==========================================================================
1309     Tooltip: class implementation
1311    ========================================================================== */
1313 /* Needed because NeXTstep does not provide enough control over tooltip
1314    display. */
1315 @implementation EmacsTooltip
1317 - init
1319   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1320                                             blue: 0.792 alpha: 0.95];
1321   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1322   NSFont *sfont = [font screenFont];
1323   int height = [sfont ascender] - [sfont descender];
1324 /*[font boundingRectForFont].size.height; */
1325   NSRect r = NSMakeRect (0, 0, 100, height+6);
1327   textField = [[NSTextField alloc] initWithFrame: r];
1328   [textField setFont: font];
1329   [textField setBackgroundColor: col];
1331   [textField setEditable: NO];
1332   [textField setSelectable: NO];
1333   [textField setBordered: NO];
1334   [textField setBezeled: NO];
1335   [textField setDrawsBackground: YES];
1337   win = [[NSWindow alloc]
1338             initWithContentRect: [textField frame]
1339                       styleMask: 0
1340                         backing: NSBackingStoreBuffered
1341                           defer: YES];
1342   [win setHasShadow: YES];
1343   [win setReleasedWhenClosed: NO];
1344   [win setDelegate: self];
1345   [[win contentView] addSubview: textField];
1346 /*  [win setBackgroundColor: col]; */
1347   [win setOpaque: NO];
1349   return self;
1352 - (void) dealloc
1354   [win close];
1355   [win release];
1356   [textField release];
1357   [super dealloc];
1360 - (void) setText: (char *)text
1362   NSString *str = [NSString stringWithUTF8String: text];
1363   NSRect r  = [textField frame];
1364   NSSize tooltipDims;
1366   [textField setStringValue: str];
1367   tooltipDims = [[textField cell] cellSize];
1369   r.size.width = tooltipDims.width;
1370   r.size.height = tooltipDims.height;
1371   [textField setFrame: r];
1374 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1376   NSRect wr = [win frame];
1378   wr.origin = NSMakePoint (x, y);
1379   wr.size = [textField frame].size;
1381   [win setFrame: wr display: YES];
1382   [win setLevel: NSPopUpMenuWindowLevel];
1383   [win orderFront: self];
1384   [win display];
1385   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1386                                          selector: @selector (hide)
1387                                          userInfo: nil repeats: NO];
1388   [timer retain];
1391 - (void) hide
1393   [win close];
1394   if (timer != nil)
1395     {
1396       if ([timer isValid])
1397         [timer invalidate];
1398       [timer release];
1399       timer = nil;
1400     }
1403 - (BOOL) isActive
1405   return timer != nil;
1408 - (NSRect) frame
1410   return [textField frame];
1413 @end  /* EmacsTooltip */
1417 /* ==========================================================================
1419     Popup Dialog: implementing functions
1421    ========================================================================== */
1423 static void
1424 pop_down_menu (void *arg)
1426   EmacsDialogPanel *panel = arg;
1428   if (popup_activated_flag)
1429     {
1430       block_input ();
1431       popup_activated_flag = 0;
1432       [panel close];
1433       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1434       unblock_input ();
1435     }
1439 Lisp_Object
1440 ns_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
1442   id dialog;
1443   Lisp_Object tem, title;
1444   NSPoint p;
1445   BOOL isQ;
1447   NSTRACE ("ns_popup_dialog");
1449   isQ = NILP (header);
1451   check_window_system (f);
1453   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1454   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1456   title = Fcar (contents);
1457   CHECK_STRING (title);
1459   if (NILP (Fcar (Fcdr (contents))))
1460     /* No buttons specified, add an "Ok" button so users can pop down
1461        the dialog.  */
1462     contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1464   block_input ();
1465   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1466                                            isQuestion: isQ];
1468   {
1469     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1471     record_unwind_protect_ptr (pop_down_menu, dialog);
1472     popup_activated_flag = 1;
1473     tem = [dialog runDialogAt: p];
1474     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1475   }
1477   unblock_input ();
1479   return tem;
1483 /* ==========================================================================
1485     Popup Dialog: class implementation
1487    ========================================================================== */
1489 @interface FlippedView : NSView
1492 @end
1494 @implementation FlippedView
1495 - (BOOL)isFlipped
1497   return YES;
1499 @end
1501 @implementation EmacsDialogPanel
1503 #define SPACER          8.0
1504 #define ICONSIZE        64.0
1505 #define TEXTHEIGHT      20.0
1506 #define MINCELLWIDTH    90.0
1508 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1509               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1511   NSSize spacing = {SPACER, SPACER};
1512   NSRect area;
1513   id cell;
1514   NSImageView *imgView;
1515   FlippedView *contentView;
1516   NSImage *img;
1518   dialog_return   = Qundefined;
1519   button_values   = NULL;
1520   area.origin.x   = 3*SPACER;
1521   area.origin.y   = 2*SPACER;
1522   area.size.width = ICONSIZE;
1523   area.size.height= ICONSIZE;
1524   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1525 #ifdef NS_IMPL_COCOA
1526 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
1527   [img setScalesWhenResized: YES];
1528 #endif
1529 #endif
1530   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1531   imgView = [[NSImageView alloc] initWithFrame: area];
1532   [imgView setImage: img];
1533   [imgView setEditable: NO];
1534   [img autorelease];
1535   [imgView autorelease];
1537   aStyle = NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask;
1538   flag = YES;
1539   rows = 0;
1540   cols = 1;
1541   [super initWithContentRect: contentRect styleMask: aStyle
1542                      backing: backingType defer: flag];
1543   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1544   [contentView autorelease];
1546   [self setContentView: contentView];
1548   [[self contentView] setAutoresizesSubviews: YES];
1550   [[self contentView] addSubview: imgView];
1551   [self setTitle: @""];
1553   area.origin.x   += ICONSIZE+2*SPACER;
1554 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1555   area.size.width = 400;
1556   area.size.height= TEXTHEIGHT;
1557   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1558   [[self contentView] addSubview: command];
1559   [command setStringValue: ns_app_name];
1560   [command setDrawsBackground: NO];
1561   [command setBezeled: NO];
1562   [command setSelectable: NO];
1563   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1565 /*  area.origin.x   = ICONSIZE+2*SPACER;
1566   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1567   area.size.width = 400;
1568   area.size.height= 2;
1569   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1570   [[self contentView] addSubview: tem];
1571   [tem setTitlePosition: NSNoTitle];
1572   [tem setAutoresizingMask: NSViewWidthSizable];*/
1574 /*  area.origin.x = ICONSIZE+2*SPACER; */
1575   area.origin.y += TEXTHEIGHT+SPACER;
1576   area.size.width = 400;
1577   area.size.height= TEXTHEIGHT;
1578   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1579   [[self contentView] addSubview: title];
1580   [title setDrawsBackground: NO];
1581   [title setBezeled: NO];
1582   [title setSelectable: NO];
1583   [title setFont: [NSFont systemFontOfSize: 11.0]];
1585   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1586   [cell setBordered: NO];
1587   [cell setEnabled: NO];
1588   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1589   [cell setBezelStyle: NSRoundedBezelStyle];
1591   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1592                                       mode: NSHighlightModeMatrix
1593                                  prototype: cell
1594                               numberOfRows: 0
1595                            numberOfColumns: 1];
1596   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1597                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1598   [matrix setIntercellSpacing: spacing];
1599   [matrix autorelease];
1601   [[self contentView] addSubview: matrix];
1602   [self setOneShot: YES];
1603   [self setReleasedWhenClosed: YES];
1604   [self setHidesOnDeactivate: YES];
1605   return self;
1609 - (BOOL)windowShouldClose: (id)sender
1611   window_closed = YES;
1612   [NSApp stop:self];
1613   return NO;
1616 - (void)dealloc
1618   xfree (button_values);
1619   [super dealloc];
1622 - (void)process_dialog: (Lisp_Object) list
1624   Lisp_Object item, lst = list;
1625   int row = 0;
1626   int buttons = 0, btnnr = 0;
1628   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1629     {
1630       item = XCAR (list);
1631       if (XTYPE (item) == Lisp_Cons)
1632         ++buttons;
1633     }
1635   if (buttons > 0)
1636     button_values = xmalloc (buttons * sizeof *button_values);
1638   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1639     {
1640       item = XCAR (list);
1641       if (XTYPE (item) == Lisp_String)
1642         {
1643           [self addString: SSDATA (item) row: row++];
1644         }
1645       else if (XTYPE (item) == Lisp_Cons)
1646         {
1647           button_values[btnnr] = XCDR (item);
1648           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1649           ++btnnr;
1650         }
1651       else if (NILP (item))
1652         {
1653           [self addSplit];
1654           row = 0;
1655         }
1656     }
1660 - (void)addButton: (char *)str value: (int)tag row: (int)row
1662   id cell;
1664   if (row >= rows)
1665     {
1666       [matrix addRow];
1667       rows++;
1668     }
1669   cell = [matrix cellAtRow: row column: cols-1];
1670   [cell setTarget: self];
1671   [cell setAction: @selector (clicked: )];
1672   [cell setTitle: [NSString stringWithUTF8String: str]];
1673   [cell setTag: tag];
1674   [cell setBordered: YES];
1675   [cell setEnabled: YES];
1679 - (void)addString: (char *)str row: (int)row
1681   id cell;
1683   if (row >= rows)
1684     {
1685       [matrix addRow];
1686       rows++;
1687     }
1688   cell = [matrix cellAtRow: row column: cols-1];
1689   [cell setTitle: [NSString stringWithUTF8String: str]];
1690   [cell setBordered: YES];
1691   [cell setEnabled: NO];
1695 - (void)addSplit
1697   [matrix addColumn];
1698   cols++;
1702 - (void)clicked: sender
1704   NSArray *sellist = nil;
1705   EMACS_INT seltag;
1707   sellist = [sender selectedCells];
1708   if ([sellist count] < 1)
1709     return;
1711   seltag = [[sellist objectAtIndex: 0] tag];
1712   dialog_return = button_values[seltag];
1713   [NSApp stop:self];
1717 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1719   Lisp_Object head;
1720   [super init];
1722   if (XTYPE (contents) == Lisp_Cons)
1723     {
1724       head = Fcar (contents);
1725       [self process_dialog: Fcdr (contents)];
1726     }
1727   else
1728     head = contents;
1730   if (XTYPE (head) == Lisp_String)
1731       [title setStringValue:
1732                  [NSString stringWithUTF8String: SSDATA (head)]];
1733   else if (isQ == YES)
1734       [title setStringValue: @"Question"];
1735   else
1736       [title setStringValue: @"Information"];
1738   {
1739     int i;
1740     NSRect r, s, t;
1742     if (cols == 1 && rows > 1)  /* Never told where to split */
1743       {
1744         [matrix addColumn];
1745         for (i = 0; i < rows/2; i++)
1746           {
1747             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1748                       atRow: i column: 1];
1749             [matrix removeRow: (rows+1)/2];
1750           }
1751       }
1753     [matrix sizeToFit];
1754     {
1755       NSSize csize = [matrix cellSize];
1756       if (csize.width < MINCELLWIDTH)
1757         {
1758           csize.width = MINCELLWIDTH;
1759           [matrix setCellSize: csize];
1760           [matrix sizeToCells];
1761         }
1762     }
1764     [title sizeToFit];
1765     [command sizeToFit];
1767     t = [matrix frame];
1768     r = [title frame];
1769     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1770       {
1771         t.origin.x   = r.origin.x;
1772         t.size.width = r.size.width;
1773       }
1774     r = [command frame];
1775     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1776       {
1777         t.origin.x   = r.origin.x;
1778         t.size.width = r.size.width;
1779       }
1781     r = [self frame];
1782     s = [(NSView *)[self contentView] frame];
1783     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1784     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1785     [self setFrame: r display: NO];
1786   }
1788   return self;
1793 - (void)timeout_handler: (NSTimer *)timedEntry
1795   NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1796                             location: NSMakePoint (0, 0)
1797                        modifierFlags: 0
1798                            timestamp: 0
1799                         windowNumber: [[NSApp mainWindow] windowNumber]
1800                              context: [NSApp context]
1801                              subtype: 0
1802                                data1: 0
1803                                data2: 0];
1805   timer_fired = YES;
1806   /* We use sto because stopModal/abortModal out of the main loop does not
1807      seem to work in 10.6.  But as we use stop we must send a real event so
1808      the stop is seen and acted upon.  */
1809   [NSApp stop:self];
1810   [NSApp postEvent: nxev atStart: NO];
1813 - (Lisp_Object)runDialogAt: (NSPoint)p
1815   Lisp_Object ret = Qundefined;
1817   while (popup_activated_flag)
1818     {
1819       NSTimer *tmo = nil;
1820       struct timespec next_time = timer_check ();
1822       if (timespec_valid_p (next_time))
1823         {
1824           double time = timespectod (next_time);
1825           tmo = [NSTimer timerWithTimeInterval: time
1826                                         target: self
1827                                       selector: @selector (timeout_handler:)
1828                                       userInfo: 0
1829                                        repeats: NO];
1830           [[NSRunLoop currentRunLoop] addTimer: tmo
1831                                        forMode: NSModalPanelRunLoopMode];
1832         }
1833       timer_fired = NO;
1834       dialog_return = Qundefined;
1835       [NSApp runModalForWindow: self];
1836       ret = dialog_return;
1837       if (! timer_fired)
1838         {
1839           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1840           break;
1841         }
1842     }
1844   if (EQ (ret, Qundefined) && window_closed)
1845     /* Make close button pressed equivalent to C-g.  */
1846     quit ();
1848   return ret;
1851 @end
1854 /* ==========================================================================
1856     Lisp definitions
1858    ========================================================================== */
1860 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1861        doc: /* Cause the NS menu to be re-calculated.  */)
1862      (void)
1864   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1865   return Qnil;
1869 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1870        doc: /* Return t if a menu or popup dialog is active.  */)
1871      (void)
1873   return popup_activated () ? Qt : Qnil;
1876 /* ==========================================================================
1878     Lisp interface declaration
1880    ========================================================================== */
1882 void
1883 syms_of_nsmenu (void)
1885 #ifndef NS_IMPL_COCOA
1886   /* Don't know how to keep track of this in Next/Open/GNUstep.  Always
1887      update menus there.  */
1888   trackingMenu = 1;
1889 #endif
1890   defsubr (&Sns_reset_menu);
1891   defsubr (&Smenu_or_popup_active_p);
1893   DEFSYM (Qdebug_on_next_call, "debug-on-next-call");