Add xref-pulse-on-jump
[emacs.git] / src / nsmenu.m
blob26fe26e5e0d811a74edfc1b239a3f1e13292ff8f
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007-2015 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
9 (at 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
48 #if 0
49 int menu_trace_num = 0;
50 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
51                                 __FILE__, __LINE__, ++menu_trace_num)
52 #else
53 #define NSTRACE(x)
54 #endif
56 #if 0
57 /* Include lisp -> C common menu parsing code */
58 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
59 #include "nsmenu_common.c"
60 #endif
62 extern long context_menu_value;
63 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
65 /* Nonzero means a menu is currently active.  */
66 static int popup_activated_flag;
68 /* Nonzero means we are tracking and updating menus.  */
69 static int trackingMenu;
72 /* NOTE: toolbar implementation is at end,
73   following complete menu implementation. */
76 /* ==========================================================================
78     Menu: Externally-called functions
80    ========================================================================== */
83 /* Supposed to discard menubar and free storage.  Since we share the
84    menubar among frames and update its context for the focused window,
85    there is nothing to do here. */
86 void
87 free_frame_menubar (struct frame *f)
89   return;
93 int
94 popup_activated (void)
96   return popup_activated_flag;
100 /* --------------------------------------------------------------------------
101     Update menubar.  Three cases:
102     1) ! deep_p, submenu = nil: Fresh switch onto a frame -- either set up
103        just top-level menu strings (OS X), or goto case (2) (GNUstep).
104     2) deep_p, submenu = nil: Recompute all submenus.
105     3) deep_p, submenu = non-nil: Update contents of a single submenu.
106    -------------------------------------------------------------------------- */
107 static void
108 ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu)
110   NSAutoreleasePool *pool;
111   id menu = [NSApp mainMenu];
112   static EmacsMenu *last_submenu = nil;
113   BOOL needsSet = NO;
114   bool owfi;
115   Lisp_Object items;
116   widget_value *wv, *first_wv, *prev_wv = 0;
117   int i;
119 #if NSMENUPROFILE
120   struct timeb tb;
121   long t;
122 #endif
124   NSTRACE (ns_update_menubar);
126   if (f != SELECTED_FRAME ())
127       return;
128   XSETFRAME (Vmenu_updating_frame, f);
129 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
131   block_input ();
132   pool = [[NSAutoreleasePool alloc] init];
134   /* Menu may have been created automatically; if so, discard it. */
135   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
136     {
137       [menu release];
138       menu = nil;
139     }
141   if (menu == nil)
142     {
143       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
144       needsSet = YES;
145     }
146   else
147     {  /* close up anything on there */
148       id attMenu = [menu attachedMenu];
149       if (attMenu != nil)
150         [attMenu close];
151     }
153 #if NSMENUPROFILE
154   ftime (&tb);
155   t = -(1000*tb.time+tb.millitm);
156 #endif
158 #ifdef NS_IMPL_GNUSTEP
159   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
160 #endif
162   if (deep_p)
163     {
164       /* Fully parse one or more of the submenus. */
165       int n = 0;
166       int *submenu_start, *submenu_end;
167       bool *submenu_top_level_items;
168       int *submenu_n_panes;
169       struct buffer *prev = current_buffer;
170       Lisp_Object buffer;
171       ptrdiff_t specpdl_count = SPECPDL_INDEX ();
172       int previous_menu_items_used = f->menu_bar_items_used;
173       Lisp_Object *previous_items
174         = alloca (previous_menu_items_used * sizeof *previous_items);
176       /* lisp preliminaries */
177       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->contents;
178       specbind (Qinhibit_quit, Qt);
179       specbind (Qdebug_on_next_call, Qnil);
180       record_unwind_save_match_data ();
181       if (NILP (Voverriding_local_map_menu_flag))
182         {
183           specbind (Qoverriding_terminal_local_map, Qnil);
184           specbind (Qoverriding_local_map, Qnil);
185         }
186       set_buffer_internal_1 (XBUFFER (buffer));
188       /* TODO: for some reason this is not needed in other terms,
189            but some menu updates call Info-extract-pointer which causes
190            abort-on-error if waiting-for-input.  Needs further investigation. */
191       owfi = waiting_for_input;
192       waiting_for_input = 0;
194       /* lucid hook and possible reset */
195       safe_run_hooks (Qactivate_menubar_hook);
196       if (! NILP (Vlucid_menu_bar_dirty_flag))
197         call0 (Qrecompute_lucid_menubar);
198       safe_run_hooks (Qmenu_bar_update_hook);
199       fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
201       /* Now ready to go */
202       items = FRAME_MENU_BAR_ITEMS (f);
204       /* Save the frame's previous menu bar contents data */
205       if (previous_menu_items_used)
206         memcpy (previous_items, aref_addr (f->menu_bar_vector, 0),
207                 previous_menu_items_used * sizeof (Lisp_Object));
209       /* parse stage 1: extract from lisp */
210       save_menu_items ();
212       menu_items = f->menu_bar_vector;
213       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
214       submenu_start = alloca (ASIZE (items) * sizeof *submenu_start);
215       submenu_end = alloca (ASIZE (items) * sizeof *submenu_end);
216       submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes);
217       submenu_top_level_items = alloca (ASIZE (items)
218                                         * sizeof *submenu_top_level_items);
219       init_menu_items ();
220       for (i = 0; i < ASIZE (items); i += 4)
221         {
222           Lisp_Object key, string, maps;
224           key = AREF (items, i);
225           string = AREF (items, i + 1);
226           maps = AREF (items, i + 2);
227           if (NILP (string))
228             break;
230           /* FIXME: we'd like to only parse the needed submenu, but this
231                was causing crashes in the _common parsing code.. need to make
232                sure proper initialization done.. */
233 /*        if (submenu && strcmp ([[submenu title] UTF8String], SSDATA (string)))
234              continue; */
236           submenu_start[i] = menu_items_used;
238           menu_items_n_panes = 0;
239           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
240           submenu_n_panes[i] = menu_items_n_panes;
241           submenu_end[i] = menu_items_used;
242           n++;
243         }
245       finish_menu_items ();
246       waiting_for_input = owfi;
249       if (submenu && n == 0)
250         {
251           /* should have found a menu for this one but didn't */
252           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
253                   [[submenu title] UTF8String]);
254           discard_menu_items ();
255           unbind_to (specpdl_count, Qnil);
256           [pool release];
257           unblock_input ();
258           return;
259         }
261       /* parse stage 2: insert into lucid 'widget_value' structures
262          [comments in other terms say not to evaluate lisp code here] */
263       wv = make_widget_value ("menubar", NULL, true, Qnil);
264       wv->button_type = BUTTON_TYPE_NONE;
265       first_wv = wv;
267       for (i = 0; i < 4*n; i += 4)
268         {
269           menu_items_n_panes = submenu_n_panes[i];
270           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
271                                       submenu_top_level_items[i]);
272           if (prev_wv)
273             prev_wv->next = wv;
274           else
275             first_wv->contents = wv;
276           /* Don't set wv->name here; GC during the loop might relocate it.  */
277           wv->enabled = 1;
278           wv->button_type = BUTTON_TYPE_NONE;
279           prev_wv = wv;
280         }
282       set_buffer_internal_1 (prev);
284       /* Compare the new menu items with previous, and leave off if no change */
285       /* FIXME: following other terms here, but seems like this should be
286            done before parse stage 2 above, since its results aren't used */
287       if (previous_menu_items_used
288           && (!submenu || (submenu && submenu == last_submenu))
289           && menu_items_used == previous_menu_items_used)
290         {
291           for (i = 0; i < previous_menu_items_used; i++)
292             /* FIXME: this ALWAYS fails on Buffers menu items.. something
293                  about their strings causes them to change every time, so we
294                  double-check failures */
295             if (!EQ (previous_items[i], AREF (menu_items, i)))
296               if (!(STRINGP (previous_items[i])
297                     && STRINGP (AREF (menu_items, i))
298                     && !strcmp (SSDATA (previous_items[i]),
299                                 SSDATA (AREF (menu_items, i)))))
300                   break;
301           if (i == previous_menu_items_used)
302             {
303               /* No change.. */
305 #if NSMENUPROFILE
306               ftime (&tb);
307               t += 1000*tb.time+tb.millitm;
308               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
309 #endif
311               free_menubar_widget_value_tree (first_wv);
312               discard_menu_items ();
313               unbind_to (specpdl_count, Qnil);
314               [pool release];
315               unblock_input ();
316               return;
317             }
318         }
319       /* The menu items are different, so store them in the frame */
320       /* FIXME: this is not correct for single-submenu case */
321       fset_menu_bar_vector (f, menu_items);
322       f->menu_bar_items_used = menu_items_used;
324       /* Calls restore_menu_items, etc., as they were outside */
325       unbind_to (specpdl_count, Qnil);
327       /* Parse stage 2a: now GC cannot happen during the lifetime of the
328          widget_value, so it's safe to store data from a Lisp_String */
329       wv = first_wv->contents;
330       for (i = 0; i < ASIZE (items); i += 4)
331         {
332           Lisp_Object string;
333           string = AREF (items, i + 1);
334           if (NILP (string))
335             break;
337           wv->name = SSDATA (string);
338           update_submenu_strings (wv->contents);
339           wv = wv->next;
340         }
342       /* Now, update the NS menu; if we have a submenu, use that, otherwise
343          create a new menu for each sub and fill it. */
344       if (submenu)
345         {
346           const char *submenuTitle = [[submenu title] UTF8String];
347           for (wv = first_wv->contents; wv; wv = wv->next)
348             {
349               if (!strcmp (submenuTitle, wv->name))
350                 {
351                   [submenu fillWithWidgetValue: wv->contents];
352                   last_submenu = submenu;
353                   break;
354                 }
355             }
356         }
357       else
358         {
359           [menu fillWithWidgetValue: first_wv->contents frame: f];
360         }
362     }
363   else
364     {
365       static int n_previous_strings = 0;
366       static char previous_strings[100][10];
367       static struct frame *last_f = NULL;
368       int n;
369       Lisp_Object string;
371       wv = make_widget_value ("menubar", NULL, true, Qnil);
372       wv->button_type = BUTTON_TYPE_NONE;
373       first_wv = wv;
375       /* Make widget-value tree w/ just the top level menu bar strings */
376       items = FRAME_MENU_BAR_ITEMS (f);
377       if (NILP (items))
378         {
379           free_menubar_widget_value_tree (first_wv);
380           [pool release];
381           unblock_input ();
382           return;
383         }
386       /* check if no change.. this mechanism is a bit rough, but ready */
387       n = ASIZE (items) / 4;
388       if (f == last_f && n_previous_strings == n)
389         {
390           for (i = 0; i<n; i++)
391             {
392               string = AREF (items, 4*i+1);
394               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
395                 continue;
396               if (NILP (string))
397                 {
398                   if (previous_strings[i][0])
399                     break;
400                   else
401                     continue;
402                 }
403               else if (memcmp (previous_strings[i], SDATA (string),
404                           min (10, SBYTES (string) + 1)))
405                 break;
406             }
408           if (i == n)
409             {
410               free_menubar_widget_value_tree (first_wv);
411               [pool release];
412               unblock_input ();
413               return;
414             }
415         }
417       [menu clear];
418       for (i = 0; i < ASIZE (items); i += 4)
419         {
420           string = AREF (items, i + 1);
421           if (NILP (string))
422             break;
424           if (n < 100)
425             memcpy (previous_strings[i/4], SDATA (string),
426                     min (10, SBYTES (string) + 1));
428           wv = make_widget_value (SSDATA (string), NULL, true, Qnil);
429           wv->button_type = BUTTON_TYPE_NONE;
430           wv->call_data = (void *) (intptr_t) (-1);
432 #ifdef NS_IMPL_COCOA
433           /* we'll update the real copy under app menu when time comes */
434           if (!strcmp ("Services", wv->name))
435             {
436               /* but we need to make sure it will update on demand */
437               [svcsMenu setFrame: f];
438             }
439           else
440 #endif
441           [menu addSubmenuWithTitle: wv->name forFrame: f];
443           if (prev_wv)
444             prev_wv->next = wv;
445           else
446             first_wv->contents = wv;
447           prev_wv = wv;
448         }
450       last_f = f;
451       if (n < 100)
452         n_previous_strings = n;
453       else
454         n_previous_strings = 0;
456     }
457   free_menubar_widget_value_tree (first_wv);
460 #if NSMENUPROFILE
461   ftime (&tb);
462   t += 1000*tb.time+tb.millitm;
463   fprintf (stderr, "Menu update took %ld msec.\n", t);
464 #endif
466   /* set main menu */
467   if (needsSet)
468     [NSApp setMainMenu: menu];
470   [pool release];
471   unblock_input ();
476 /* Main emacs core entry point for menubar menus: called to indicate that the
477    frame's menus have changed, and the *step representation should be updated
478    from Lisp. */
479 void
480 set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
482   ns_update_menubar (f, deep_p, nil);
485 void
486 x_activate_menubar (struct frame *f)
488 #ifdef NS_IMPL_COCOA
489   ns_update_menubar (f, true, nil);
490   ns_check_pending_open_menu ();
491 #endif
497 /* ==========================================================================
499     Menu: class implementation
501    ========================================================================== */
504 /* Menu that can define itself from Emacs "widget_value"s and will lazily
505    update itself when user clicked.  Based on Carbon/AppKit implementation
506    by Yamamoto Mitsuharu. */
507 @implementation EmacsMenu
509 /* override designated initializer */
510 - initWithTitle: (NSString *)title
512   frame = 0;
513   if ((self = [super initWithTitle: title]))
514     [self setAutoenablesItems: NO];
515   return self;
519 /* used for top-level */
520 - initWithTitle: (NSString *)title frame: (struct frame *)f
522   [self initWithTitle: title];
523   frame = f;
524 #ifdef NS_IMPL_COCOA
525   [self setDelegate: self];
526 #endif
527   return self;
531 - (void)setFrame: (struct frame *)f
533   frame = f;
536 #ifdef NS_IMPL_COCOA
537 -(void)trackingNotification:(NSNotification *)notification
539   /* Update menu in menuNeedsUpdate only while tracking menus.  */
540   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
541                   ? 1 : 0);
542   if (! trackingMenu) ns_check_menu_open (nil);
545 - (void)menuWillOpen:(NSMenu *)menu
547   ++trackingMenu;
549 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
550   // On 10.6 we get repeated calls, only the one for NSSystemDefined is "real".
551   if ([[NSApp currentEvent] type] != NSSystemDefined) return;
552 #endif
554   /* When dragging from one menu to another, we get willOpen followed by didClose,
555      i.e. trackingMenu == 3 in willOpen and then 2 after didClose.
556      We have updated all menus, so avoid doing it when trackingMenu == 3.  */
557   if (trackingMenu == 2)
558     ns_check_menu_open (menu);
561 - (void)menuDidClose:(NSMenu *)menu
563   --trackingMenu;
566 #endif /* NS_IMPL_COCOA */
568 /* delegate method called when a submenu is being opened: run a 'deep' call
569    to set_frame_menubar */
570 - (void)menuNeedsUpdate: (NSMenu *)menu
572   if (!FRAME_LIVE_P (frame))
573     return;
575   /* Cocoa/Carbon will request update on every keystroke
576      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
577      since key equivalents are handled through emacs.
578      On Leopard, even keystroke events generate SystemDefined event.
579      Third-party applications that enhance mouse / trackpad
580      interaction, or also VNC/Remote Desktop will send events
581      of type AppDefined rather than SysDefined.
582      Menus will fail to show up if they haven't been initialized.
583      AppDefined events may lack timing data.
585      Thus, we rely on the didBeginTrackingNotification notification
586      as above to indicate the need for updates.
587      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
588      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
589   */
590   if (trackingMenu == 0)
591     return;
592 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
593 #ifdef NS_IMPL_GNUSTEP
594   /* Don't know how to do this for anything other than OSX >= 10.5
595      This is wrong, as it might run Lisp code in the event loop.  */
596   ns_update_menubar (frame, true, self);
597 #endif
601 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
603   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
604       && FRAME_NS_VIEW (SELECTED_FRAME ()))
605     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
606   return YES;
610 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
611    into an accelerator string.  We are only able to display a single character
612    for an accelerator, together with an optional modifier combination.  (Under
613    Carbon more control was possible, but in Cocoa multi-char strings passed to
614    NSMenuItem get ignored.  For now we try to display a super-single letter
615    combo, and return the others as strings to be appended to the item title.
616    (This is signaled by setting keyEquivModMask to 0 for now.) */
617 -(NSString *)parseKeyEquiv: (const char *)key
619   const char *tpos = key;
620   keyEquivModMask = NSCommandKeyMask;
622   if (!key || !strlen (key))
623     return @"";
625   while (*tpos == ' ' || *tpos == '(')
626     tpos++;
627   if ((*tpos == 's') && (*(tpos+1) == '-'))
628     {
629       return [NSString stringWithFormat: @"%c", tpos[2]];
630     }
631   keyEquivModMask = 0; /* signal */
632   return [NSString stringWithUTF8String: tpos];
636 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
638   NSMenuItem *item;
639   widget_value *wv = (widget_value *)wvptr;
641   if (menu_separator_name_p (wv->name))
642     {
643       item = [NSMenuItem separatorItem];
644       [self addItem: item];
645     }
646   else
647     {
648       NSString *title, *keyEq;
649       title = [NSString stringWithUTF8String: wv->name];
650       if (title == nil)
651         title = @"< ? >";  /* (get out in the open so we know about it) */
653       keyEq = [self parseKeyEquiv: wv->key];
654 #ifdef NS_IMPL_COCOA
655       /* OS X just ignores modifier strings longer than one character */
656       if (keyEquivModMask == 0)
657         title = [title stringByAppendingFormat: @" (%@)", keyEq];
658 #endif
660       item = [self addItemWithTitle: (NSString *)title
661                              action: @selector (menuDown:)
662                       keyEquivalent: keyEq];
663       [item setKeyEquivalentModifierMask: keyEquivModMask];
665       [item setEnabled: wv->enabled];
667       /* Draw radio buttons and tickboxes */
668       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
669                            wv->button_type == BUTTON_TYPE_RADIO))
670         [item setState: NSOnState];
671       else
672         [item setState: NSOffState];
674       [item setTag: (NSInteger)wv->call_data];
675     }
677   return item;
681 /* convenience */
682 -(void)clear
684   int n;
686   for (n = [self numberOfItems]-1; n >= 0; n--)
687     {
688       NSMenuItem *item = [self itemAtIndex: n];
689       NSString *title = [item title];
690       if ([ns_app_name isEqualToString: title]
691           && ![item isSeparatorItem])
692         continue;
693       [self removeItemAtIndex: n];
694     }
698 - (void)fillWithWidgetValue: (void *)wvptr
700   [self fillWithWidgetValue: wvptr frame: (struct frame *)nil];
703 - (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f
705   widget_value *wv = (widget_value *)wvptr;
707   /* clear existing contents */
708   [self setMenuChangedMessagesEnabled: NO];
709   [self clear];
711   /* add new contents */
712   for (; wv != NULL; wv = wv->next)
713     {
714       NSMenuItem *item = [self addItemWithWidgetValue: wv];
716       if (wv->contents)
717         {
718           EmacsMenu *submenu;
720           if (f)
721             submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f];
722           else
723             submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
725           [self setSubmenu: submenu forItem: item];
726           [submenu fillWithWidgetValue: wv->contents];
727           [submenu release];
728           [item setAction: (SEL)nil];
729         }
730     }
732   [self setMenuChangedMessagesEnabled: YES];
733 #ifdef NS_IMPL_GNUSTEP
734   if ([[self window] isVisible])
735     [self sizeToFit];
736 #endif
740 /* adds an empty submenu and returns it */
741 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
743   NSString *titleStr = [NSString stringWithUTF8String: title];
744   NSMenuItem *item = [self addItemWithTitle: titleStr
745                                      action: (SEL)nil /*@selector (menuDown:) */
746                               keyEquivalent: @""];
747   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
748   [self setSubmenu: submenu forItem: item];
749   [submenu release];
750   return submenu;
753 /* run a menu in popup mode */
754 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
755                  keymaps: (bool)keymaps
757   EmacsView *view = FRAME_NS_VIEW (f);
758   NSEvent *e, *event;
759   long retVal;
761 /*   p = [view convertPoint:p fromView: nil]; */
762   p.y = NSHeight ([view frame]) - p.y;
763   e = [[view window] currentEvent];
764    event = [NSEvent mouseEventWithType: NSRightMouseDown
765                               location: p
766                          modifierFlags: 0
767                              timestamp: [e timestamp]
768                           windowNumber: [[view window] windowNumber]
769                                context: [e context]
770                            eventNumber: 0/*[e eventNumber] */
771                             clickCount: 1
772                               pressure: 0];
774   context_menu_value = -1;
775   [NSMenu popUpContextMenu: self withEvent: event forView: view];
776   retVal = context_menu_value;
777   context_menu_value = 0;
778   return retVal > 0
779       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
780       : Qnil;
783 @end  /* EmacsMenu */
787 /* ==========================================================================
789     Context Menu: implementing functions
791    ========================================================================== */
793 Lisp_Object
794 ns_menu_show (struct frame *f, int x, int y, int menuflags,
795               Lisp_Object title, const char **error)
797   EmacsMenu *pmenu;
798   NSPoint p;
799   Lisp_Object tem;
800   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
801   widget_value *wv, *first_wv = 0;
802   bool keymaps = (menuflags & MENU_KEYMAPS);
804   block_input ();
806   p.x = x; p.y = y;
808   /* now parse stage 2 as in ns_update_menubar */
809   wv = make_widget_value ("contextmenu", NULL, true, Qnil);
810   wv->button_type = BUTTON_TYPE_NONE;
811   first_wv = wv;
813 #if 0
814   /* FIXME: a couple of one-line differences prevent reuse */
815   wv = digest_single_submenu (0, menu_items_used, 0);
816 #else
817   {
818   widget_value *save_wv = 0, *prev_wv = 0;
819   widget_value **submenu_stack
820     = alloca (menu_items_used * sizeof *submenu_stack);
821 /*   Lisp_Object *subprefix_stack
822        = alloca (menu_items_used * sizeof *subprefix_stack); */
823   int submenu_depth = 0;
824   int first_pane = 1;
825   int i;
827   /* Loop over all panes and items, filling in the tree.  */
828   i = 0;
829   while (i < menu_items_used)
830     {
831       if (EQ (AREF (menu_items, i), Qnil))
832         {
833           submenu_stack[submenu_depth++] = save_wv;
834           save_wv = prev_wv;
835           prev_wv = 0;
836           first_pane = 1;
837           i++;
838         }
839       else if (EQ (AREF (menu_items, i), Qlambda))
840         {
841           prev_wv = save_wv;
842           save_wv = submenu_stack[--submenu_depth];
843           first_pane = 0;
844           i++;
845         }
846       else if (EQ (AREF (menu_items, i), Qt)
847                && submenu_depth != 0)
848         i += MENU_ITEMS_PANE_LENGTH;
849       /* Ignore a nil in the item list.
850          It's meaningful only for dialog boxes.  */
851       else if (EQ (AREF (menu_items, i), Qquote))
852         i += 1;
853       else if (EQ (AREF (menu_items, i), Qt))
854         {
855           /* Create a new pane.  */
856           Lisp_Object pane_name, prefix;
857           const char *pane_string;
859           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
860           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
862 #ifndef HAVE_MULTILINGUAL_MENU
863           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
864             {
865               pane_name = ENCODE_MENU_STRING (pane_name);
866               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
867             }
868 #endif
869           pane_string = (NILP (pane_name)
870                          ? "" : SSDATA (pane_name));
871           /* If there is just one top-level pane, put all its items directly
872              under the top-level menu.  */
873           if (menu_items_n_panes == 1)
874             pane_string = "";
876           /* If the pane has a meaningful name,
877              make the pane a top-level menu item
878              with its items as a submenu beneath it.  */
879           if (!keymaps && strcmp (pane_string, ""))
880             {
881               wv = make_widget_value (pane_string, NULL, true, Qnil);
882               if (save_wv)
883                 save_wv->next = wv;
884               else
885                 first_wv->contents = wv;
886               if (keymaps && !NILP (prefix))
887                 wv->name++;
888               wv->button_type = BUTTON_TYPE_NONE;
889               save_wv = wv;
890               prev_wv = 0;
891             }
892           else if (first_pane)
893             {
894               save_wv = wv;
895               prev_wv = 0;
896             }
897           first_pane = 0;
898           i += MENU_ITEMS_PANE_LENGTH;
899         }
900       else
901         {
902           /* Create a new item within current pane.  */
903           Lisp_Object item_name, enable, descrip, def, type, selected, help;
904           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
905           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
906           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
907           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
908           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
909           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
910           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
912 #ifndef HAVE_MULTILINGUAL_MENU
913           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
914             {
915               item_name = ENCODE_MENU_STRING (item_name);
916               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
917             }
919           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
920             {
921               descrip = ENCODE_MENU_STRING (descrip);
922               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
923             }
924 #endif /* not HAVE_MULTILINGUAL_MENU */
926           wv = make_widget_value (SSDATA (item_name), NULL, !NILP (enable),
927                                   STRINGP (help) ? help : Qnil);
928           if (prev_wv)
929             prev_wv->next = wv;
930           else
931             save_wv->contents = wv;
932           if (!NILP (descrip))
933             wv->key = SSDATA (descrip);
934           /* If this item has a null value,
935              make the call_data null so that it won't display a box
936              when the mouse is on it.  */
937           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
939           if (NILP (type))
940             wv->button_type = BUTTON_TYPE_NONE;
941           else if (EQ (type, QCtoggle))
942             wv->button_type = BUTTON_TYPE_TOGGLE;
943           else if (EQ (type, QCradio))
944             wv->button_type = BUTTON_TYPE_RADIO;
945           else
946             emacs_abort ();
948           wv->selected = !NILP (selected);
950           prev_wv = wv;
952           i += MENU_ITEMS_ITEM_LENGTH;
953         }
954     }
955   }
956 #endif
958   if (!NILP (title))
959     {
960       widget_value *wv_title;
961       widget_value *wv_sep = make_widget_value ("--", NULL, false, Qnil);
963       /* Maybe replace this separator with a bitmap or owner-draw item
964          so that it looks better.  Having two separators looks odd.  */
965       wv_sep->next = first_wv->contents;
967 #ifndef HAVE_MULTILINGUAL_MENU
968       if (STRING_MULTIBYTE (title))
969         title = ENCODE_MENU_STRING (title);
970 #endif
971       wv_title = make_widget_value (SSDATA (title), NULL, false, Qnil);
972       wv_title->button_type = BUTTON_TYPE_NONE;
973       wv_title->next = wv_sep;
974       first_wv->contents = wv_title;
975     }
977   pmenu = [[EmacsMenu alloc] initWithTitle:
978                                [NSString stringWithUTF8String: SSDATA (title)]];
979   [pmenu fillWithWidgetValue: first_wv->contents];
980   free_menubar_widget_value_tree (first_wv);
981   unbind_to (specpdl_count, Qnil);
983   popup_activated_flag = 1;
984   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
985   popup_activated_flag = 0;
986   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
988   unblock_input ();
989   return tem;
993 /* ==========================================================================
995     Toolbar: externally-called functions
997    ========================================================================== */
999 void
1000 free_frame_tool_bar (struct frame *f)
1001 /* --------------------------------------------------------------------------
1002     Under NS we just hide the toolbar until it might be needed again.
1003    -------------------------------------------------------------------------- */
1005   EmacsView *view = FRAME_NS_VIEW (f);
1006   block_input ();
1007   view->wait_for_tool_bar = NO;
1008   [[view toolbar] setVisible: NO];
1009   FRAME_TOOLBAR_HEIGHT (f) = 0;
1010   unblock_input ();
1013 void
1014 update_frame_tool_bar (struct frame *f)
1015 /* --------------------------------------------------------------------------
1016     Update toolbar contents
1017    -------------------------------------------------------------------------- */
1019   int i, k = 0;
1020   EmacsView *view = FRAME_NS_VIEW (f);
1021   NSWindow *window = [view window];
1022   EmacsToolbar *toolbar = [view toolbar];
1023   int oldh;
1025   if (view == nil || toolbar == nil) return;
1026   block_input ();
1028   oldh = FRAME_TOOLBAR_HEIGHT (f);
1030 #ifdef NS_IMPL_COCOA
1031   [toolbar clearActive];
1032 #else
1033   [toolbar clearAll];
1034 #endif
1036   /* update EmacsToolbar as in GtkUtils, build items list */
1037   for (i = 0; i < f->n_tool_bar_items; ++i)
1038     {
1039 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1040                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1042       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1043       int idx;
1044       ptrdiff_t img_id;
1045       struct image *img;
1046       Lisp_Object image;
1047       Lisp_Object helpObj;
1048       const char *helpText;
1050       /* Check if this is a separator.  */
1051       if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1052         {
1053           /* Skip separators.  Newer OSX don't show them, and on GNUstep they
1054              are wide as a button, thus overflowing the toolbar most of
1055              the time.  */
1056           continue;
1057         }
1059       /* If image is a vector, choose the image according to the
1060          button state.  */
1061       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1062       if (VECTORP (image))
1063         {
1064           /* NS toolbar auto-computes disabled and selected images */
1065           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1066           eassert (ASIZE (image) >= idx);
1067           image = AREF (image, idx);
1068         }
1069       else
1070         {
1071           idx = -1;
1072         }
1073       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1074       if (NILP (helpObj))
1075         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1076       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1078       /* Ignore invalid image specifications.  */
1079       if (!valid_image_p (image))
1080         {
1081           /* Don't log anything, GNUS makes invalid images all the time.  */
1082           continue;
1083         }
1085       img_id = lookup_image (f, image);
1086       img = IMAGE_FROM_ID (f, img_id);
1087       prepare_image_for_display (f, img);
1089       if (img->load_failed_p || img->pixmap == nil)
1090         {
1091           NSLog (@"Could not prepare toolbar image for display.");
1092           continue;
1093         }
1095       [toolbar addDisplayItemWithImage: img->pixmap
1096                                    idx: k++
1097                                    tag: i
1098                               helpText: helpText
1099                                enabled: enabled_p];
1100 #undef TOOLPROP
1101     }
1103   if (![toolbar isVisible])
1104       [toolbar setVisible: YES];
1106 #ifdef NS_IMPL_COCOA
1107   if ([toolbar changed])
1108     {
1109       /* inform app that toolbar has changed */
1110       NSDictionary *dict = [toolbar configurationDictionary];
1111       NSMutableDictionary *newDict = [dict mutableCopy];
1112       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1113       id key;
1114       while ((key = [keys nextObject]) != nil)
1115         {
1116           NSObject *val = [dict objectForKey: key];
1117           if ([val isKindOfClass: [NSArray class]])
1118             {
1119               [newDict setObject:
1120                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1121                           forKey: key];
1122               break;
1123             }
1124         }
1125       [toolbar setConfigurationFromDictionary: newDict];
1126       [newDict release];
1127     }
1128 #endif
1130   FRAME_TOOLBAR_HEIGHT (f) =
1131     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1132     - FRAME_NS_TITLEBAR_HEIGHT (f);
1133   if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1134     FRAME_TOOLBAR_HEIGHT (f) = 0;
1136   if (oldh != FRAME_TOOLBAR_HEIGHT (f))
1137     [view updateFrameSize:YES];
1138   if (view->wait_for_tool_bar && FRAME_TOOLBAR_HEIGHT (f) > 0)
1139     {
1140       view->wait_for_tool_bar = NO;
1141       [view setNeedsDisplay: YES];
1142     }
1144   unblock_input ();
1148 /* ==========================================================================
1150     Toolbar: class implementation
1152    ========================================================================== */
1154 @implementation EmacsToolbar
1156 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1158   self = [super initWithIdentifier: identifier];
1159   emacsView = view;
1160   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1161   [self setSizeMode: NSToolbarSizeModeSmall];
1162   [self setDelegate: self];
1163   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1164   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1165   prevIdentifiers = nil;
1166   prevEnablement = enablement = 0L;
1167   return self;
1170 - (void)dealloc
1172   [prevIdentifiers release];
1173   [activeIdentifiers release];
1174   [identifierToItem release];
1175   [super dealloc];
1178 - (void) clearActive
1180   [prevIdentifiers release];
1181   prevIdentifiers = [activeIdentifiers copy];
1182   [activeIdentifiers removeAllObjects];
1183   prevEnablement = enablement;
1184   enablement = 0L;
1187 - (void) clearAll
1189   [self clearActive];
1190   while ([[self items] count] > 0)
1191     [self removeItemAtIndex: 0];
1194 - (BOOL) changed
1196   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1197     enablement == prevEnablement ? NO : YES;
1200 - (void) addDisplayItemWithImage: (EmacsImage *)img
1201                              idx: (int)idx
1202                              tag: (int)tag
1203                         helpText: (const char *)help
1204                          enabled: (BOOL)enabled
1206   /* 1) come up w/identifier */
1207   NSString *identifier
1208     = [NSString stringWithFormat: @"%lu", (unsigned long)[img hash]];
1209   [activeIdentifiers addObject: identifier];
1211   /* 2) create / reuse item */
1212   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1213   if (item == nil)
1214     {
1215       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1216                autorelease];
1217       [item setImage: img];
1218       [item setToolTip: [NSString stringWithUTF8String: help]];
1219       [item setTarget: emacsView];
1220       [item setAction: @selector (toolbarClicked:)];
1221       [identifierToItem setObject: item forKey: identifier];
1222     }
1224 #ifdef NS_IMPL_GNUSTEP
1225   [self insertItemWithItemIdentifier: identifier atIndex: idx];
1226 #endif
1228   [item setTag: tag];
1229   [item setEnabled: enabled];
1231   /* 3) update state */
1232   enablement = (enablement << 1) | (enabled == YES);
1235 /* This overrides super's implementation, which automatically sets
1236    all items to enabled state (for some reason). */
1237 - (void)validateVisibleItems
1242 /* delegate methods */
1244 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1245       itemForItemIdentifier: (NSString *)itemIdentifier
1246   willBeInsertedIntoToolbar: (BOOL)flag
1248   /* look up NSToolbarItem by identifier and return... */
1249   return [identifierToItem objectForKey: itemIdentifier];
1252 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1254   /* return entire set.. */
1255   return activeIdentifiers;
1258 /* for configuration palette (not yet supported) */
1259 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1261   /* return entire set... */
1262   return activeIdentifiers;
1263   //return [identifierToItem allKeys];
1266 /* optional and unneeded */
1267 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1268 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1269 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1271 @end  /* EmacsToolbar */
1275 /* ==========================================================================
1277     Tooltip: class implementation
1279    ========================================================================== */
1281 /* Needed because NeXTstep does not provide enough control over tooltip
1282    display. */
1283 @implementation EmacsTooltip
1285 - init
1287   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1288                                             blue: 0.792 alpha: 0.95];
1289   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1290   NSFont *sfont = [font screenFont];
1291   int height = [sfont ascender] - [sfont descender];
1292 /*[font boundingRectForFont].size.height; */
1293   NSRect r = NSMakeRect (0, 0, 100, height+6);
1295   textField = [[NSTextField alloc] initWithFrame: r];
1296   [textField setFont: font];
1297   [textField setBackgroundColor: col];
1299   [textField setEditable: NO];
1300   [textField setSelectable: NO];
1301   [textField setBordered: NO];
1302   [textField setBezeled: NO];
1303   [textField setDrawsBackground: YES];
1305   win = [[NSWindow alloc]
1306             initWithContentRect: [textField frame]
1307                       styleMask: 0
1308                         backing: NSBackingStoreBuffered
1309                           defer: YES];
1310   [win setHasShadow: YES];
1311   [win setReleasedWhenClosed: NO];
1312   [win setDelegate: self];
1313   [[win contentView] addSubview: textField];
1314 /*  [win setBackgroundColor: col]; */
1315   [win setOpaque: NO];
1317   return self;
1320 - (void) dealloc
1322   [win close];
1323   [win release];
1324   [textField release];
1325   [super dealloc];
1328 - (void) setText: (char *)text
1330   NSString *str = [NSString stringWithUTF8String: text];
1331   NSRect r  = [textField frame];
1332   NSSize tooltipDims;
1334   [textField setStringValue: str];
1335   tooltipDims = [[textField cell] cellSize];
1337   r.size.width = tooltipDims.width;
1338   r.size.height = tooltipDims.height;
1339   [textField setFrame: r];
1342 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1344   NSRect wr = [win frame];
1346   wr.origin = NSMakePoint (x, y);
1347   wr.size = [textField frame].size;
1349   [win setFrame: wr display: YES];
1350   [win setLevel: NSPopUpMenuWindowLevel];
1351   [win orderFront: self];
1352   [win display];
1353   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1354                                          selector: @selector (hide)
1355                                          userInfo: nil repeats: NO];
1356   [timer retain];
1359 - (void) hide
1361   [win close];
1362   if (timer != nil)
1363     {
1364       if ([timer isValid])
1365         [timer invalidate];
1366       [timer release];
1367       timer = nil;
1368     }
1371 - (BOOL) isActive
1373   return timer != nil;
1376 - (NSRect) frame
1378   return [textField frame];
1381 @end  /* EmacsTooltip */
1385 /* ==========================================================================
1387     Popup Dialog: implementing functions
1389    ========================================================================== */
1391 struct Popdown_data
1393   NSAutoreleasePool *pool;
1394   EmacsDialogPanel *dialog;
1397 static void
1398 pop_down_menu (void *arg)
1400   struct Popdown_data *unwind_data = arg;
1402   block_input ();
1403   if (popup_activated_flag)
1404     {
1405       EmacsDialogPanel *panel = unwind_data->dialog;
1406       popup_activated_flag = 0;
1407       [panel close];
1408       [unwind_data->pool release];
1409       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1410     }
1412   xfree (unwind_data);
1413   unblock_input ();
1417 Lisp_Object
1418 ns_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
1420   id dialog;
1421   Lisp_Object window, tem, title;
1422   NSPoint p;
1423   BOOL isQ;
1424   NSAutoreleasePool *pool;
1426   NSTRACE (x-popup-dialog);
1428   isQ = NILP (header);
1430   check_window_system (f);
1432   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1433   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1435   title = Fcar (contents);
1436   CHECK_STRING (title);
1438   if (NILP (Fcar (Fcdr (contents))))
1439     /* No buttons specified, add an "Ok" button so users can pop down
1440        the dialog.  */
1441     contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1443   block_input ();
1444   pool = [[NSAutoreleasePool alloc] init];
1445   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1446                                            isQuestion: isQ];
1448   {
1449     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1450     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1452     unwind_data->pool = pool;
1453     unwind_data->dialog = dialog;
1455     record_unwind_protect_ptr (pop_down_menu, unwind_data);
1456     popup_activated_flag = 1;
1457     tem = [dialog runDialogAt: p];
1458     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1459   }
1461   unblock_input ();
1463   return tem;
1467 /* ==========================================================================
1469     Popup Dialog: class implementation
1471    ========================================================================== */
1473 @interface FlippedView : NSView
1476 @end
1478 @implementation FlippedView
1479 - (BOOL)isFlipped
1481   return YES;
1483 @end
1485 @implementation EmacsDialogPanel
1487 #define SPACER          8.0
1488 #define ICONSIZE        64.0
1489 #define TEXTHEIGHT      20.0
1490 #define MINCELLWIDTH    90.0
1492 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1493               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1495   NSSize spacing = {SPACER, SPACER};
1496   NSRect area;
1497   id cell;
1498   NSImageView *imgView;
1499   FlippedView *contentView;
1500   NSImage *img;
1502   dialog_return   = Qundefined;
1503   button_values   = NULL;
1504   area.origin.x   = 3*SPACER;
1505   area.origin.y   = 2*SPACER;
1506   area.size.width = ICONSIZE;
1507   area.size.height= ICONSIZE;
1508   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1509   [img setScalesWhenResized: YES];
1510   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1511   imgView = [[NSImageView alloc] initWithFrame: area];
1512   [imgView setImage: img];
1513   [imgView setEditable: NO];
1514   [img autorelease];
1515   [imgView autorelease];
1517   aStyle = NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask;
1518   flag = YES;
1519   rows = 0;
1520   cols = 1;
1521   [super initWithContentRect: contentRect styleMask: aStyle
1522                      backing: backingType defer: flag];
1523   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1524   [contentView autorelease];
1526   [self setContentView: contentView];
1528   [[self contentView] setAutoresizesSubviews: YES];
1530   [[self contentView] addSubview: imgView];
1531   [self setTitle: @""];
1533   area.origin.x   += ICONSIZE+2*SPACER;
1534 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1535   area.size.width = 400;
1536   area.size.height= TEXTHEIGHT;
1537   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1538   [[self contentView] addSubview: command];
1539   [command setStringValue: ns_app_name];
1540   [command setDrawsBackground: NO];
1541   [command setBezeled: NO];
1542   [command setSelectable: NO];
1543   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1545 /*  area.origin.x   = ICONSIZE+2*SPACER;
1546   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1547   area.size.width = 400;
1548   area.size.height= 2;
1549   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1550   [[self contentView] addSubview: tem];
1551   [tem setTitlePosition: NSNoTitle];
1552   [tem setAutoresizingMask: NSViewWidthSizable];*/
1554 /*  area.origin.x = ICONSIZE+2*SPACER; */
1555   area.origin.y += TEXTHEIGHT+SPACER;
1556   area.size.width = 400;
1557   area.size.height= TEXTHEIGHT;
1558   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1559   [[self contentView] addSubview: title];
1560   [title setDrawsBackground: NO];
1561   [title setBezeled: NO];
1562   [title setSelectable: NO];
1563   [title setFont: [NSFont systemFontOfSize: 11.0]];
1565   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1566   [cell setBordered: NO];
1567   [cell setEnabled: NO];
1568   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1569   [cell setBezelStyle: NSRoundedBezelStyle];
1571   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1572                                       mode: NSHighlightModeMatrix
1573                                  prototype: cell
1574                               numberOfRows: 0
1575                            numberOfColumns: 1];
1576   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1577                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1578   [matrix setIntercellSpacing: spacing];
1579   [matrix autorelease];
1581   [[self contentView] addSubview: matrix];
1582   [self setOneShot: YES];
1583   [self setReleasedWhenClosed: YES];
1584   [self setHidesOnDeactivate: YES];
1585   return self;
1589 - (BOOL)windowShouldClose: (id)sender
1591   window_closed = YES;
1592   [NSApp stop:self];
1593   return NO;
1596 - (void)dealloc
1598   xfree (button_values);
1599   [super dealloc];
1602 - (void)process_dialog: (Lisp_Object) list
1604   Lisp_Object item, lst = list;
1605   int row = 0;
1606   int buttons = 0, btnnr = 0;
1608   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1609     {
1610       item = XCAR (list);
1611       if (XTYPE (item) == Lisp_Cons)
1612         ++buttons;
1613     }
1615   if (buttons > 0)
1616     button_values = xmalloc (buttons * sizeof *button_values);
1618   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1619     {
1620       item = XCAR (list);
1621       if (XTYPE (item) == Lisp_String)
1622         {
1623           [self addString: SSDATA (item) row: row++];
1624         }
1625       else if (XTYPE (item) == Lisp_Cons)
1626         {
1627           button_values[btnnr] = XCDR (item);
1628           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1629           ++btnnr;
1630         }
1631       else if (NILP (item))
1632         {
1633           [self addSplit];
1634           row = 0;
1635         }
1636     }
1640 - (void)addButton: (char *)str value: (int)tag row: (int)row
1642   id cell;
1644   if (row >= rows)
1645     {
1646       [matrix addRow];
1647       rows++;
1648     }
1649   cell = [matrix cellAtRow: row column: cols-1];
1650   [cell setTarget: self];
1651   [cell setAction: @selector (clicked: )];
1652   [cell setTitle: [NSString stringWithUTF8String: str]];
1653   [cell setTag: tag];
1654   [cell setBordered: YES];
1655   [cell setEnabled: YES];
1659 - (void)addString: (char *)str row: (int)row
1661   id cell;
1663   if (row >= rows)
1664     {
1665       [matrix addRow];
1666       rows++;
1667     }
1668   cell = [matrix cellAtRow: row column: cols-1];
1669   [cell setTitle: [NSString stringWithUTF8String: str]];
1670   [cell setBordered: YES];
1671   [cell setEnabled: NO];
1675 - (void)addSplit
1677   [matrix addColumn];
1678   cols++;
1682 - (void)clicked: sender
1684   NSArray *sellist = nil;
1685   EMACS_INT seltag;
1687   sellist = [sender selectedCells];
1688   if ([sellist count] < 1)
1689     return;
1691   seltag = [[sellist objectAtIndex: 0] tag];
1692   dialog_return = button_values[seltag];
1693   [NSApp stop:self];
1697 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1699   Lisp_Object head;
1700   [super init];
1702   if (XTYPE (contents) == Lisp_Cons)
1703     {
1704       head = Fcar (contents);
1705       [self process_dialog: Fcdr (contents)];
1706     }
1707   else
1708     head = contents;
1710   if (XTYPE (head) == Lisp_String)
1711       [title setStringValue:
1712                  [NSString stringWithUTF8String: SSDATA (head)]];
1713   else if (isQ == YES)
1714       [title setStringValue: @"Question"];
1715   else
1716       [title setStringValue: @"Information"];
1718   {
1719     int i;
1720     NSRect r, s, t;
1722     if (cols == 1 && rows > 1)  /* Never told where to split */
1723       {
1724         [matrix addColumn];
1725         for (i = 0; i < rows/2; i++)
1726           {
1727             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1728                       atRow: i column: 1];
1729             [matrix removeRow: (rows+1)/2];
1730           }
1731       }
1733     [matrix sizeToFit];
1734     {
1735       NSSize csize = [matrix cellSize];
1736       if (csize.width < MINCELLWIDTH)
1737         {
1738           csize.width = MINCELLWIDTH;
1739           [matrix setCellSize: csize];
1740           [matrix sizeToCells];
1741         }
1742     }
1744     [title sizeToFit];
1745     [command sizeToFit];
1747     t = [matrix frame];
1748     r = [title frame];
1749     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1750       {
1751         t.origin.x   = r.origin.x;
1752         t.size.width = r.size.width;
1753       }
1754     r = [command frame];
1755     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1756       {
1757         t.origin.x   = r.origin.x;
1758         t.size.width = r.size.width;
1759       }
1761     r = [self frame];
1762     s = [(NSView *)[self contentView] frame];
1763     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1764     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1765     [self setFrame: r display: NO];
1766   }
1768   return self;
1773 - (void)timeout_handler: (NSTimer *)timedEntry
1775   NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1776                             location: NSMakePoint (0, 0)
1777                        modifierFlags: 0
1778                            timestamp: 0
1779                         windowNumber: [[NSApp mainWindow] windowNumber]
1780                              context: [NSApp context]
1781                              subtype: 0
1782                                data1: 0
1783                                data2: 0];
1785   timer_fired = YES;
1786   /* We use sto because stopModal/abortModal out of the main loop does not
1787      seem to work in 10.6.  But as we use stop we must send a real event so
1788      the stop is seen and acted upon.  */
1789   [NSApp stop:self];
1790   [NSApp postEvent: nxev atStart: NO];
1793 - (Lisp_Object)runDialogAt: (NSPoint)p
1795   Lisp_Object ret = Qundefined;
1797   while (popup_activated_flag)
1798     {
1799       NSTimer *tmo = nil;
1800       struct timespec next_time = timer_check ();
1802       if (timespec_valid_p (next_time))
1803         {
1804           double time = timespectod (next_time);
1805           tmo = [NSTimer timerWithTimeInterval: time
1806                                         target: self
1807                                       selector: @selector (timeout_handler:)
1808                                       userInfo: 0
1809                                        repeats: NO];
1810           [[NSRunLoop currentRunLoop] addTimer: tmo
1811                                        forMode: NSModalPanelRunLoopMode];
1812         }
1813       timer_fired = NO;
1814       dialog_return = Qundefined;
1815       [NSApp runModalForWindow: self];
1816       ret = dialog_return;
1817       if (! timer_fired)
1818         {
1819           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1820           break;
1821         }
1822     }
1824   if (EQ (ret, Qundefined) && window_closed)
1825     /* Make close button pressed equivalent to C-g.  */
1826     Fsignal (Qquit, Qnil);
1828   return ret;
1831 @end
1834 /* ==========================================================================
1836     Lisp definitions
1838    ========================================================================== */
1840 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1841        doc: /* Cause the NS menu to be re-calculated.  */)
1842      (void)
1844   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1845   return Qnil;
1849 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1850        doc: /* Return t if a menu or popup dialog is active.  */)
1851      (void)
1853   return popup_activated () ? Qt : Qnil;
1856 /* ==========================================================================
1858     Lisp interface declaration
1860    ========================================================================== */
1862 void
1863 syms_of_nsmenu (void)
1865 #ifndef NS_IMPL_COCOA
1866   /* Don't know how to keep track of this in Next/Open/GNUstep.  Always
1867      update menus there.  */
1868   trackingMenu = 1;
1869 #endif
1870   defsubr (&Sns_reset_menu);
1871   defsubr (&Smenu_or_popup_active_p);
1873   DEFSYM (Qdebug_on_next_call, "debug-on-next-call");