Copyedits for Internals and OS chapters of Lisp manual.
[emacs.git] / src / nsmenu.m
blob7a6434941d2d1f457436157bdc33f14bd5e70fad
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007-2012 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>
27 #include <setjmp.h>
29 #include "lisp.h"
30 #include "window.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 #define MenuStagger 10.0
50 #if 0
51 int menu_trace_num = 0;
52 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
53                                 __FILE__, __LINE__, ++menu_trace_num)
54 #else
55 #define NSTRACE(x)
56 #endif
58 #if 0
59 /* Include lisp -> C common menu parsing code */
60 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
61 #include "nsmenu_common.c"
62 #endif
64 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
65 extern Lisp_Object QCtoggle, QCradio;
67 Lisp_Object Qdebug_on_next_call;
68 extern Lisp_Object Qoverriding_local_map, Qoverriding_terminal_local_map;
70 extern long context_menu_value;
71 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
73 /* Nonzero means a menu is currently active.  */
74 static int popup_activated_flag;
75 static NSModalSession popupSession;
77 /* Nonzero means we are tracking and updating menus.  */
78 static int trackingMenu;
81 /* NOTE: toolbar implementation is at end,
82   following complete menu implementation. */
85 /* ==========================================================================
87     Menu: Externally-called functions
89    ========================================================================== */
92 /* FIXME: not currently used, but should normalize with other terms. */
93 void
94 x_activate_menubar (struct frame *f)
96     fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
100 /* Supposed to discard menubar and free storage.  Since we share the
101    menubar among frames and update its context for the focused window,
102    there is nothing to do here. */
103 void
104 free_frame_menubar (struct frame *f)
106   return;
111 popup_activated (void)
113   return popup_activated_flag;
117 /* --------------------------------------------------------------------------
118     Update menubar.  Three cases:
119     1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
120        just top-level menu strings (OS X), or goto case (2) (GNUstep).
121     2) deep_p = 1, submenu = nil: Recompute all submenus.
122     3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
123    -------------------------------------------------------------------------- */
124 void
125 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
127   NSAutoreleasePool *pool;
128   id menu = [NSApp mainMenu];
129   static EmacsMenu *last_submenu = nil;
130   BOOL needsSet = NO;
131   const char *submenuTitle = [[submenu title] UTF8String];
132   extern int waiting_for_input;
133   int owfi;
134   Lisp_Object items;
135   widget_value *wv, *first_wv, *prev_wv = 0;
136   int i;
138 #if NSMENUPROFILE
139   struct timeb tb;
140   long t;
141 #endif
143   NSTRACE (set_frame_menubar);
145   if (f != SELECTED_FRAME ())
146       return;
147   XSETFRAME (Vmenu_updating_frame, f);
148 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
150   BLOCK_INPUT;
151   pool = [[NSAutoreleasePool alloc] init];
153   /* Menu may have been created automatically; if so, discard it. */
154   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
155     {
156       [menu release];
157       menu = nil;
158     }
160   if (menu == nil)
161     {
162       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
163       needsSet = YES;
164     }
165   else
166     {  /* close up anything on there */
167       id attMenu = [menu attachedMenu];
168       if (attMenu != nil)
169         [attMenu close];
170     }
172 #if NSMENUPROFILE
173   ftime (&tb);
174   t = -(1000*tb.time+tb.millitm);
175 #endif
177 #ifdef NS_IMPL_GNUSTEP
178   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
179 #endif
181   if (deep_p)
182     {
183       /* Fully parse one or more of the submenus. */
184       int n = 0;
185       int *submenu_start, *submenu_end;
186       int *submenu_top_level_items, *submenu_n_panes;
187       struct buffer *prev = current_buffer;
188       Lisp_Object buffer;
189       int specpdl_count = SPECPDL_INDEX ();
190       int previous_menu_items_used = f->menu_bar_items_used;
191       Lisp_Object *previous_items
192         = (Lisp_Object *) alloca (previous_menu_items_used
193                                   * sizeof (Lisp_Object));
195       /* lisp preliminaries */
196       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
197       specbind (Qinhibit_quit, Qt);
198       specbind (Qdebug_on_next_call, Qnil);
199       record_unwind_save_match_data ();
200       if (NILP (Voverriding_local_map_menu_flag))
201         {
202           specbind (Qoverriding_terminal_local_map, Qnil);
203           specbind (Qoverriding_local_map, Qnil);
204         }
205       set_buffer_internal_1 (XBUFFER (buffer));
207       /* TODO: for some reason this is not needed in other terms,
208            but some menu updates call Info-extract-pointer which causes
209            abort-on-error if waiting-for-input.  Needs further investigation. */
210       owfi = waiting_for_input;
211       waiting_for_input = 0;
213       /* lucid hook and possible reset */
214       safe_run_hooks (Qactivate_menubar_hook);
215       if (! NILP (Vlucid_menu_bar_dirty_flag))
216         call0 (Qrecompute_lucid_menubar);
217       safe_run_hooks (Qmenu_bar_update_hook);
218       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
220       /* Now ready to go */
221       items = FRAME_MENU_BAR_ITEMS (f);
223       /* Save the frame's previous menu bar contents data */
224       if (previous_menu_items_used)
225         memcpy (previous_items, &AREF (f->menu_bar_vector, 0),
226                 previous_menu_items_used * sizeof (Lisp_Object));
228       /* parse stage 1: extract from lisp */
229       save_menu_items ();
231       menu_items = f->menu_bar_vector;
232       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
233       submenu_start = (int *) alloca (ASIZE (items) * sizeof (int *));
234       submenu_end = (int *) alloca (ASIZE (items) * sizeof (int *));
235       submenu_n_panes = (int *) alloca (ASIZE (items) * sizeof (int));
236       submenu_top_level_items
237         = (int *) alloca (ASIZE (items) * sizeof (int *));
238       init_menu_items ();
239       for (i = 0; i < ASIZE (items); i += 4)
240         {
241           Lisp_Object key, string, maps;
243           key = AREF (items, i);
244           string = AREF (items, i + 1);
245           maps = AREF (items, i + 2);
246           if (NILP (string))
247             break;
249           /* FIXME: we'd like to only parse the needed submenu, but this
250                was causing crashes in the _common parsing code.. need to make
251                sure proper initialization done.. */
252 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
253              continue; */
255           submenu_start[i] = menu_items_used;
257           menu_items_n_panes = 0;
258           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
259           submenu_n_panes[i] = menu_items_n_panes;
260           submenu_end[i] = menu_items_used;
261           n++;
262         }
264       finish_menu_items ();
265       waiting_for_input = owfi;
268       if (submenu && n == 0)
269         {
270           /* should have found a menu for this one but didn't */
271           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
272                   submenuTitle);
273           discard_menu_items ();
274           unbind_to (specpdl_count, Qnil);
275           [pool release];
276           UNBLOCK_INPUT;
277           return;
278         }
280       /* parse stage 2: insert into lucid 'widget_value' structures
281          [comments in other terms say not to evaluate lisp code here] */
282       wv = xmalloc_widget_value ();
283       wv->name = "menubar";
284       wv->value = 0;
285       wv->enabled = 1;
286       wv->button_type = BUTTON_TYPE_NONE;
287       wv->help = Qnil;
288       first_wv = wv;
290       for (i = 0; i < 4*n; i += 4)
291         {
292           menu_items_n_panes = submenu_n_panes[i];
293           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
294                                       submenu_top_level_items[i]);
295           if (prev_wv)
296             prev_wv->next = wv;
297           else
298             first_wv->contents = wv;
299           /* Don't set wv->name here; GC during the loop might relocate it.  */
300           wv->enabled = 1;
301           wv->button_type = BUTTON_TYPE_NONE;
302           prev_wv = wv;
303         }
305       set_buffer_internal_1 (prev);
307       /* Compare the new menu items with previous, and leave off if no change */
308       /* FIXME: following other terms here, but seems like this should be
309            done before parse stage 2 above, since its results aren't used */
310       if (previous_menu_items_used
311           && (!submenu || (submenu && submenu == last_submenu))
312           && menu_items_used == previous_menu_items_used)
313         {
314           for (i = 0; i < previous_menu_items_used; i++)
315             /* FIXME: this ALWAYS fails on Buffers menu items.. something
316                  about their strings causes them to change every time, so we
317                  double-check failures */
318             if (!EQ (previous_items[i], AREF (menu_items, i)))
319               if (!(STRINGP (previous_items[i])
320                     && STRINGP (AREF (menu_items, i))
321                     && !strcmp (SDATA (previous_items[i]),
322                                 SDATA (AREF (menu_items, i)))))
323                   break;
324           if (i == previous_menu_items_used)
325             {
326               /* No change.. */
328 #if NSMENUPROFILE
329               ftime (&tb);
330               t += 1000*tb.time+tb.millitm;
331               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
332 #endif
334               free_menubar_widget_value_tree (first_wv);
335               discard_menu_items ();
336               unbind_to (specpdl_count, Qnil);
337               [pool release];
338               UNBLOCK_INPUT;
339               return;
340             }
341         }
342       /* The menu items are different, so store them in the frame */
343       /* FIXME: this is not correct for single-submenu case */
344       f->menu_bar_vector = menu_items;
345       f->menu_bar_items_used = menu_items_used;
347       /* Calls restore_menu_items, etc., as they were outside */
348       unbind_to (specpdl_count, Qnil);
350       /* Parse stage 2a: now GC cannot happen during the lifetime of the
351          widget_value, so it's safe to store data from a Lisp_String */
352       wv = first_wv->contents;
353       for (i = 0; i < ASIZE (items); i += 4)
354         {
355           Lisp_Object string;
356           string = AREF (items, i + 1);
357           if (NILP (string))
358             break;
359 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
360                continue; */
362           wv->name = SSDATA (string);
363           update_submenu_strings (wv->contents);
364           wv = wv->next;
365         }
367       /* Now, update the NS menu; if we have a submenu, use that, otherwise
368          create a new menu for each sub and fill it. */
369       if (submenu)
370         {
371           for (wv = first_wv->contents; wv; wv = wv->next)
372             {
373               if (!strcmp (submenuTitle, wv->name))
374                 {
375                   [submenu fillWithWidgetValue: wv->contents];
376                   last_submenu = submenu;
377                   break;
378                 }
379             }
380         }
381       else
382         {
383           [menu fillWithWidgetValue: first_wv->contents];
384         }
386     }
387   else
388     {
389       static int n_previous_strings = 0;
390       static char previous_strings[100][10];
391       static struct frame *last_f = NULL;
392       int n;
393       Lisp_Object string;
395       wv = xmalloc_widget_value ();
396       wv->name = "menubar";
397       wv->value = 0;
398       wv->enabled = 1;
399       wv->button_type = BUTTON_TYPE_NONE;
400       wv->help = Qnil;
401       first_wv = wv;
403       /* Make widget-value tree w/ just the top level menu bar strings */
404       items = FRAME_MENU_BAR_ITEMS (f);
405       if (NILP (items))
406         {
407           free_menubar_widget_value_tree (first_wv);
408           [pool release];
409           UNBLOCK_INPUT;
410           return;
411         }
414       /* check if no change.. this mechanism is a bit rough, but ready */
415       n = ASIZE (items) / 4;
416       if (f == last_f && n_previous_strings == n)
417         {
418           for (i = 0; i<n; i++)
419             {
420               string = AREF (items, 4*i+1);
422               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
423                 continue;
424               if (NILP (string))
425                 if (previous_strings[i][0])
426                   break;
427               else
428                 continue;
429               if (strncmp (previous_strings[i], SDATA (string), 10))
430                 break;
431             }
433           if (i == n)
434             {
435               free_menubar_widget_value_tree (first_wv);
436               [pool release];
437               UNBLOCK_INPUT;
438               return;
439             }
440         }
442       [menu clear];
443       for (i = 0; i < ASIZE (items); i += 4)
444         {
445           string = AREF (items, i + 1);
446           if (NILP (string))
447             break;
449           if (n < 100)
450             strncpy (previous_strings[i/4], SDATA (string), 10);
452           wv = xmalloc_widget_value ();
453           wv->name = SSDATA (string);
454           wv->value = 0;
455           wv->enabled = 1;
456           wv->button_type = BUTTON_TYPE_NONE;
457           wv->help = Qnil;
458           wv->call_data = (void *) (EMACS_INT) (-1);
460 #ifdef NS_IMPL_COCOA
461           /* we'll update the real copy under app menu when time comes */
462           if (!strcmp ("Services", wv->name))
463             {
464               /* but we need to make sure it will update on demand */
465               [svcsMenu setFrame: f];
466             }
467           else
468 #endif
469           [menu addSubmenuWithTitle: wv->name forFrame: f];
471           if (prev_wv)
472             prev_wv->next = wv;
473           else
474             first_wv->contents = wv;
475           prev_wv = wv;
476         }
478       last_f = f;
479       if (n < 100)
480         n_previous_strings = n;
481       else
482         n_previous_strings = 0;
484     }
485   free_menubar_widget_value_tree (first_wv);
488 #if NSMENUPROFILE
489   ftime (&tb);
490   t += 1000*tb.time+tb.millitm;
491   fprintf (stderr, "Menu update took %ld msec.\n", t);
492 #endif
494   /* set main menu */
495   if (needsSet)
496     [NSApp setMainMenu: menu];
498   [pool release];
499   UNBLOCK_INPUT;
504 /* Main emacs core entry point for menubar menus: called to indicate that the
505    frame's menus have changed, and the *step representation should be updated
506    from Lisp. */
507 void
508 set_frame_menubar (struct frame *f, int first_time, int deep_p)
510   ns_update_menubar (f, deep_p, nil);
514 /* ==========================================================================
516     Menu: class implementation
518    ========================================================================== */
521 /* Menu that can define itself from Emacs "widget_value"s and will lazily
522    update itself when user clicked.  Based on Carbon/AppKit implementation
523    by Yamamoto Mitsuharu. */
524 @implementation EmacsMenu
526 /* override designated initializer */
527 - initWithTitle: (NSString *)title
529   if (self = [super initWithTitle: title])
530     [self setAutoenablesItems: NO];
531   return self;
535 /* used for top-level */
536 - initWithTitle: (NSString *)title frame: (struct frame *)f
538   [self initWithTitle: title];
539   frame = f;
540 #ifdef NS_IMPL_COCOA
541   [self setDelegate: self];
542 #endif
543   return self;
547 - (void)setFrame: (struct frame *)f
549   frame = f;
552 #ifdef NS_IMPL_COCOA
553 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
554 extern NSString *NSMenuDidBeginTrackingNotification;
555 #endif
556 #endif
558 #ifdef NS_IMPL_COCOA
559 -(void)trackingNotification:(NSNotification *)notification
561   /* Update menu in menuNeedsUpdate only while tracking menus.  */
562   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
563                   ? 1 : 0);
565 #endif
567 /* delegate method called when a submenu is being opened: run a 'deep' call
568    to set_frame_menubar */
569 - (void)menuNeedsUpdate: (NSMenu *)menu
571   if (!FRAME_LIVE_P (frame))
572     return;
574   /* Cocoa/Carbon will request update on every keystroke
575      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
576      since key equivalents are handled through emacs.
577      On Leopard, even keystroke events generate SystemDefined event.
578      Third-party applications that enhance mouse / trackpad
579      interaction, or also VNC/Remote Desktop will send events
580      of type AppDefined rather than SysDefined.
581      Menus will fail to show up if they haven't been initialized.
582      AppDefined events may lack timing data.
584      Thus, we rely on the didBeginTrackingNotification notification
585      as above to indicate the need for updates.
586      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
587      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
588   */
589   if (trackingMenu == 0
590       /* Also, don't try this if from an event picked up asynchronously,
591          as lots of lisp evaluation happens in ns_update_menubar. */
592       || handling_signal != 0)
593     return;
594 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
595   ns_update_menubar (frame, 1, self);
599 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
601   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
602       && FRAME_NS_VIEW (SELECTED_FRAME ()))
603     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
604   return YES;
608 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
609    into an accelerator string.  We are only able to display a single character
610    for an accelerator, together with an optional modifier combination.  (Under
611    Carbon more control was possible, but in Cocoa multi-char strings passed to
612    NSMenuItem get ignored.  For now we try to display a super-single letter
613    combo, and return the others as strings to be appended to the item title.
614    (This is signaled by setting keyEquivModMask to 0 for now.) */
615 -(NSString *)parseKeyEquiv: (const char *)key
617   const char *tpos = key;
618   keyEquivModMask = NSCommandKeyMask;
620   if (!key || !strlen (key))
621     return @"";
623   while (*tpos == ' ' || *tpos == '(')
624     tpos++;
625   if ((*tpos == 's') && (*(tpos+1) == '-'))
626     {
627       return [NSString stringWithFormat: @"%c", tpos[2]];
628     }
629   keyEquivModMask = 0; /* signal */
630   return [NSString stringWithUTF8String: tpos];
634 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
636   NSMenuItem *item;
637   widget_value *wv = (widget_value *)wvptr;
639   if (menu_separator_name_p (wv->name))
640     {
641       item = [NSMenuItem separatorItem];
642       [self addItem: item];
643     }
644   else
645     {
646       NSString *title, *keyEq;
647       title = [NSString stringWithUTF8String: wv->name];
648       if (title == nil)
649         title = @"< ? >";  /* (get out in the open so we know about it) */
651       keyEq = [self parseKeyEquiv: wv->key];
652 #ifdef NS_IMPL_COCOA
653       /* OS X just ignores modifier strings longer than one character */
654       if (keyEquivModMask == 0)
655         title = [title stringByAppendingFormat: @" (%@)", keyEq];
656 #endif
658       item = [self addItemWithTitle: (NSString *)title
659                              action: @selector (menuDown:)
660                       keyEquivalent: keyEq];
661       [item setKeyEquivalentModifierMask: keyEquivModMask];
663       [item setEnabled: wv->enabled];
665       /* Draw radio buttons and tickboxes */
666       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
667                            wv->button_type == BUTTON_TYPE_RADIO))
668         [item setState: NSOnState];
669       else
670         [item setState: NSOffState];
672       [item setTag: (NSInteger)wv->call_data];
673     }
675   return item;
679 /* convenience */
680 -(void)clear
682   int n;
684   for (n = [self numberOfItems]-1; n >= 0; n--)
685     {
686       NSMenuItem *item = [self itemAtIndex: n];
687       NSString *title = [item title];
688       if (([title length] == 0  /* OSX 10.5 */
689            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
690            || [@"Apple" isEqualToString: title]) /* older */
691           && ![item isSeparatorItem])
692         continue;
693       [self removeItemAtIndex: n];
694     }
698 - (void)fillWithWidgetValue: (void *)wvptr
700   widget_value *wv = (widget_value *)wvptr;
702   /* clear existing contents */
703   [self setMenuChangedMessagesEnabled: NO];
704   [self clear];
706   /* add new contents */
707   for (; wv != NULL; wv = wv->next)
708     {
709       NSMenuItem *item = [self addItemWithWidgetValue: wv];
711       if (wv->contents)
712         {
713           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
715           [self setSubmenu: submenu forItem: item];
716           [submenu fillWithWidgetValue: wv->contents];
717           [submenu release];
718           [item setAction: nil];
719         }
720     }
722   [self setMenuChangedMessagesEnabled: YES];
723 #ifdef NS_IMPL_GNUSTEP
724   if ([[self window] isVisible])
725     [self sizeToFit];
726 #else
727 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_2
728   if ([self supermenu] == nil)
729     [self sizeToFit];
730 #endif
731 #endif
735 /* adds an empty submenu and returns it */
736 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
738   NSString *titleStr = [NSString stringWithUTF8String: title];
739   NSMenuItem *item = [self addItemWithTitle: titleStr
740                                      action: nil /*@selector (menuDown:) */
741                               keyEquivalent: @""];
742   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
743   [self setSubmenu: submenu forItem: item];
744   [submenu release];
745   return submenu;
748 /* run a menu in popup mode */
749 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
750                  keymaps: (int)keymaps
752   EmacsView *view = FRAME_NS_VIEW (f);
753   NSEvent *e, *event;
754   long retVal;
756 /*   p = [view convertPoint:p fromView: nil]; */
757   p.y = NSHeight ([view frame]) - p.y;
758   e = [[view window] currentEvent];
759    event = [NSEvent mouseEventWithType: NSRightMouseDown
760                               location: p
761                          modifierFlags: 0
762                              timestamp: [e timestamp]
763                           windowNumber: [[view window] windowNumber]
764                                context: [e context]
765                            eventNumber: 0/*[e eventNumber] */
766                             clickCount: 1
767                               pressure: 0];
769   context_menu_value = -1;
770   [NSMenu popUpContextMenu: self withEvent: event forView: view];
771   retVal = context_menu_value;
772   context_menu_value = 0;
773   return retVal > 0
774       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
775       : Qnil;
778 @end  /* EmacsMenu */
782 /* ==========================================================================
784     Context Menu: implementing functions
786    ========================================================================== */
788 Lisp_Object
789 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
790               Lisp_Object title, const char **error)
792   EmacsMenu *pmenu;
793   NSPoint p;
794   Lisp_Object window, tem, keymap;
795   int specpdl_count = SPECPDL_INDEX ();
796   widget_value *wv, *first_wv = 0;
798   p.x = x; p.y = y;
800   /* now parse stage 2 as in ns_update_menubar */
801   wv = xmalloc_widget_value ();
802   wv->name = "contextmenu";
803   wv->value = 0;
804   wv->enabled = 1;
805   wv->button_type = BUTTON_TYPE_NONE;
806   wv->help = Qnil;
807   first_wv = wv;
809 #if 0
810   /* FIXME: a couple of one-line differences prevent reuse */
811   wv = digest_single_submenu (0, menu_items_used, Qnil);
812 #else
813   {
814   widget_value *save_wv = 0, *prev_wv = 0;
815   widget_value **submenu_stack
816     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
817 /*   Lisp_Object *subprefix_stack
818        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
819   int submenu_depth = 0;
820   int first_pane = 1;
821   int i;
823   /* Loop over all panes and items, filling in the tree.  */
824   i = 0;
825   while (i < menu_items_used)
826     {
827       if (EQ (AREF (menu_items, i), Qnil))
828         {
829           submenu_stack[submenu_depth++] = save_wv;
830           save_wv = prev_wv;
831           prev_wv = 0;
832           first_pane = 1;
833           i++;
834         }
835       else if (EQ (AREF (menu_items, i), Qlambda))
836         {
837           prev_wv = save_wv;
838           save_wv = submenu_stack[--submenu_depth];
839           first_pane = 0;
840           i++;
841         }
842       else if (EQ (AREF (menu_items, i), Qt)
843                && submenu_depth != 0)
844         i += MENU_ITEMS_PANE_LENGTH;
845       /* Ignore a nil in the item list.
846          It's meaningful only for dialog boxes.  */
847       else if (EQ (AREF (menu_items, i), Qquote))
848         i += 1;
849       else if (EQ (AREF (menu_items, i), Qt))
850         {
851           /* Create a new pane.  */
852           Lisp_Object pane_name, prefix;
853           const char *pane_string;
855           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
856           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
858 #ifndef HAVE_MULTILINGUAL_MENU
859           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
860             {
861               pane_name = ENCODE_MENU_STRING (pane_name);
862               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
863             }
864 #endif
865           pane_string = (NILP (pane_name)
866                          ? "" : SSDATA (pane_name));
867           /* If there is just one top-level pane, put all its items directly
868              under the top-level menu.  */
869           if (menu_items_n_panes == 1)
870             pane_string = "";
872           /* If the pane has a meaningful name,
873              make the pane a top-level menu item
874              with its items as a submenu beneath it.  */
875           if (!keymaps && strcmp (pane_string, ""))
876             {
877               wv = xmalloc_widget_value ();
878               if (save_wv)
879                 save_wv->next = wv;
880               else
881                 first_wv->contents = wv;
882               wv->name = pane_string;
883               if (keymaps && !NILP (prefix))
884                 wv->name++;
885               wv->value = 0;
886               wv->enabled = 1;
887               wv->button_type = BUTTON_TYPE_NONE;
888               wv->help = Qnil;
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 = xmalloc_widget_value ();
927           if (prev_wv)
928             prev_wv->next = wv;
929           else
930             save_wv->contents = wv;
931           wv->name = SSDATA (item_name);
932           if (!NILP (descrip))
933             wv->key = SSDATA (descrip);
934           wv->value = 0;
935           /* If this item has a null value,
936              make the call_data null so that it won't display a box
937              when the mouse is on it.  */
938           wv->call_data
939               = !NILP (def) ? (void *) &AREF (menu_items, i) : 0;
940           wv->enabled = !NILP (enable);
942           if (NILP (type))
943             wv->button_type = BUTTON_TYPE_NONE;
944           else if (EQ (type, QCtoggle))
945             wv->button_type = BUTTON_TYPE_TOGGLE;
946           else if (EQ (type, QCradio))
947             wv->button_type = BUTTON_TYPE_RADIO;
948           else
949             abort ();
951           wv->selected = !NILP (selected);
953           if (! STRINGP (help))
954             help = Qnil;
956           wv->help = help;
958           prev_wv = wv;
960           i += MENU_ITEMS_ITEM_LENGTH;
961         }
962     }
963   }
964 #endif
966   if (!NILP (title))
967     {
968       widget_value *wv_title = xmalloc_widget_value ();
969       widget_value *wv_sep = xmalloc_widget_value ();
971       /* Maybe replace this separator with a bitmap or owner-draw item
972          so that it looks better.  Having two separators looks odd.  */
973       wv_sep->name = "--";
974       wv_sep->next = first_wv->contents;
975       wv_sep->help = Qnil;
977 #ifndef HAVE_MULTILINGUAL_MENU
978       if (STRING_MULTIBYTE (title))
979         title = ENCODE_MENU_STRING (title);
980 #endif
982       wv_title->name = SSDATA (title);
983       wv_title->enabled = NO;
984       wv_title->button_type = BUTTON_TYPE_NONE;
985       wv_title->help = Qnil;
986       wv_title->next = wv_sep;
987       first_wv->contents = wv_title;
988     }
990   pmenu = [[EmacsMenu alloc] initWithTitle:
991                                [NSString stringWithUTF8String: SDATA (title)]];
992   [pmenu fillWithWidgetValue: first_wv->contents];
993   free_menubar_widget_value_tree (first_wv);
994   unbind_to (specpdl_count, Qnil);
996   popup_activated_flag = 1;
997   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
998   popup_activated_flag = 0;
999   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1001   return tem;
1005 /* ==========================================================================
1007     Toolbar: externally-called functions
1009    ========================================================================== */
1011 void
1012 free_frame_tool_bar (FRAME_PTR f)
1013 /* --------------------------------------------------------------------------
1014     Under NS we just hide the toolbar until it might be needed again.
1015    -------------------------------------------------------------------------- */
1017   BLOCK_INPUT;
1018   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1019   FRAME_TOOLBAR_HEIGHT (f) = 0;
1020   UNBLOCK_INPUT;
1023 void
1024 update_frame_tool_bar (FRAME_PTR f)
1025 /* --------------------------------------------------------------------------
1026     Update toolbar contents
1027    -------------------------------------------------------------------------- */
1029   int i;
1030   EmacsView *view = FRAME_NS_VIEW (f);
1031   NSWindow *window = [view window];
1032   EmacsToolbar *toolbar = [view toolbar];
1034   BLOCK_INPUT;
1035   [toolbar clearActive];
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       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1045       int idx;
1046       ptrdiff_t img_id;
1047       struct image *img;
1048       Lisp_Object image;
1049       Lisp_Object helpObj;
1050       const char *helpText;
1052       /* If image is a vector, choose the image according to the
1053          button state.  */
1054       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1055       if (VECTORP (image))
1056         {
1057           /* NS toolbar auto-computes disabled and selected images */
1058           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1059           xassert (ASIZE (image) >= idx);
1060           image = AREF (image, idx);
1061         }
1062       else
1063         {
1064           idx = -1;
1065         }
1066       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1067       if (NILP (helpObj))
1068         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1069       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1071       /* Ignore invalid image specifications.  */
1072       if (!valid_image_p (image))
1073         {
1074           /* Don't log anything, GNUS makes invalid images all the time.  */
1075           continue;
1076         }
1078       img_id = lookup_image (f, image);
1079       img = IMAGE_FROM_ID (f, img_id);
1080       prepare_image_for_display (f, img);
1082       if (img->load_failed_p || img->pixmap == nil)
1083         {
1084           NSLog (@"Could not prepare toolbar image for display.");
1085           continue;
1086         }
1088       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1089                                enabled: enabled_p];
1090 #undef TOOLPROP
1091     }
1093   if (![toolbar isVisible])
1094       [toolbar setVisible: YES];
1096   if ([toolbar changed])
1097     {
1098       /* inform app that toolbar has changed */
1099       NSDictionary *dict = [toolbar configurationDictionary];
1100       NSMutableDictionary *newDict = [dict mutableCopy];
1101       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1102       NSObject *key;
1103       while ((key = [keys nextObject]) != nil)
1104         {
1105           NSObject *val = [dict objectForKey: key];
1106           if ([val isKindOfClass: [NSArray class]])
1107             {
1108               [newDict setObject:
1109                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1110                           forKey: key];
1111               break;
1112             }
1113         }
1114       [toolbar setConfigurationFromDictionary: newDict];
1115       [newDict release];
1116     }
1118   FRAME_TOOLBAR_HEIGHT (f) =
1119     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1120     - FRAME_NS_TITLEBAR_HEIGHT (f);
1121   UNBLOCK_INPUT;
1125 /* ==========================================================================
1127     Toolbar: class implementation
1129    ========================================================================== */
1131 @implementation EmacsToolbar
1133 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1135   self = [super initWithIdentifier: identifier];
1136   emacsView = view;
1137   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1138   [self setSizeMode: NSToolbarSizeModeSmall];
1139   [self setDelegate: self];
1140   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1141   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1142   prevEnablement = enablement = 0L;
1143   return self;
1146 - (void)dealloc
1148   [prevIdentifiers release];
1149   [activeIdentifiers release];
1150   [identifierToItem release];
1151   [super dealloc];
1154 - (void) clearActive
1156   [prevIdentifiers release];
1157   prevIdentifiers = [activeIdentifiers copy];
1158   [activeIdentifiers removeAllObjects];
1159   prevEnablement = enablement;
1160   enablement = 0L;
1163 - (BOOL) changed
1165   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1166     enablement == prevEnablement ? NO : YES;
1169 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1170                         helpText: (const char *)help enabled: (BOOL)enabled
1172   /* 1) come up w/identifier */
1173   NSString *identifier
1174       = [NSString stringWithFormat: @"%u", [img hash]];
1176   /* 2) create / reuse item */
1177   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1178   if (item == nil)
1179     {
1180       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1181                autorelease];
1182       [item setImage: img];
1183       [item setToolTip: [NSString stringWithUTF8String: help]];
1184       [item setTarget: emacsView];
1185       [item setAction: @selector (toolbarClicked:)];
1186     }
1188   [item setTag: idx];
1189   [item setEnabled: enabled];
1191   /* 3) update state */
1192   [identifierToItem setObject: item forKey: identifier];
1193   [activeIdentifiers addObject: identifier];
1194   enablement = (enablement << 1) | (enabled == YES);
1197 /* This overrides super's implementation, which automatically sets
1198    all items to enabled state (for some reason). */
1199 - (void)validateVisibleItems { }
1202 /* delegate methods */
1204 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1205       itemForItemIdentifier: (NSString *)itemIdentifier
1206   willBeInsertedIntoToolbar: (BOOL)flag
1208   /* look up NSToolbarItem by identifier and return... */
1209   return [identifierToItem objectForKey: itemIdentifier];
1212 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1214   /* return entire set.. */
1215   return activeIdentifiers;
1218 /* for configuration palette (not yet supported) */
1219 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1221   /* return entire set... */
1222   return [identifierToItem allKeys];
1225 /* optional and unneeded */
1226 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1227 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1228 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1230 @end  /* EmacsToolbar */
1234 /* ==========================================================================
1236     Tooltip: class implementation
1238    ========================================================================== */
1240 /* Needed because NeXTstep does not provide enough control over tooltip
1241    display. */
1242 @implementation EmacsTooltip
1244 - init
1246   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1247                                             blue: 0.792 alpha: 0.95];
1248   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1249   NSFont *sfont = [font screenFont];
1250   int height = [sfont ascender] - [sfont descender];
1251 /*[font boundingRectForFont].size.height; */
1252   NSRect r = NSMakeRect (0, 0, 100, height+6);
1254   textField = [[NSTextField alloc] initWithFrame: r];
1255   [textField setFont: font];
1256   [textField setBackgroundColor: col];
1258   [textField setEditable: NO];
1259   [textField setSelectable: NO];
1260   [textField setBordered: NO];
1261   [textField setBezeled: NO];
1262   [textField setDrawsBackground: YES];
1264   win = [[NSWindow alloc]
1265             initWithContentRect: [textField frame]
1266                       styleMask: 0
1267                         backing: NSBackingStoreBuffered
1268                           defer: YES];
1269   [win setHasShadow: YES];
1270   [win setReleasedWhenClosed: NO];
1271   [win setDelegate: self];
1272   [[win contentView] addSubview: textField];
1273 /*  [win setBackgroundColor: col]; */
1274   [win setOpaque: NO];
1276   return self;
1279 - (void) dealloc
1281   [win close];
1282   [win release];
1283   [textField release];
1284   [super dealloc];
1287 - (void) setText: (char *)text
1289   NSString *str = [NSString stringWithUTF8String: text];
1290   NSRect r  = [textField frame];
1291   NSSize tooltipDims;
1293   [textField setStringValue: str];
1294   tooltipDims = [[textField cell] cellSize];
1296   r.size.width = tooltipDims.width;
1297   r.size.height = tooltipDims.height;
1298   [textField setFrame: r];
1301 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1303   NSRect wr = [win frame];
1305   wr.origin = NSMakePoint (x, y);
1306   wr.size = [textField frame].size;
1308   [win setFrame: wr display: YES];
1309   [win orderFront: self];
1310   [win display];
1311   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1312                                          selector: @selector (hide)
1313                                          userInfo: nil repeats: NO];
1314   [timer retain];
1317 - (void) hide
1319   [win close];
1320   if (timer != nil)
1321     {
1322       if ([timer isValid])
1323         [timer invalidate];
1324       [timer release];
1325       timer = nil;
1326     }
1329 - (BOOL) isActive
1331   return timer != nil;
1334 - (NSRect) frame
1336   return [textField frame];
1339 @end  /* EmacsTooltip */
1343 /* ==========================================================================
1345     Popup Dialog: implementing functions
1347    ========================================================================== */
1350 static Lisp_Object
1351 pop_down_menu (Lisp_Object arg)
1353   struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1354   if (popup_activated_flag)
1355     {
1356       popup_activated_flag = 0;
1357       BLOCK_INPUT;
1358       [NSApp endModalSession: popupSession];
1359       [((EmacsDialogPanel *) (p->pointer)) close];
1360       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1361       UNBLOCK_INPUT;
1362     }
1363   return Qnil;
1367 Lisp_Object
1368 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1370   id dialog;
1371   Lisp_Object window, tem, title;
1372   struct frame *f;
1373   NSPoint p;
1374   BOOL isQ;
1376   NSTRACE (x-popup-dialog);
1378   check_ns ();
1380   isQ = NILP (header);
1382   if (EQ (position, Qt)
1383       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1384                                || EQ (XCAR (position), Qtool_bar))))
1385     {
1386       window = selected_window;
1387     }
1388   else if (CONSP (position))
1389     {
1390       Lisp_Object tem;
1391       tem = Fcar (position);
1392       if (XTYPE (tem) == Lisp_Cons)
1393         window = Fcar (Fcdr (position));
1394       else
1395         {
1396           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1397           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1398         }
1399     }
1400   else if (WINDOWP (position) || FRAMEP (position))
1401     {
1402       window = position;
1403     }
1404   else
1405     window = Qnil;
1407   if (FRAMEP (window))
1408     f = XFRAME (window);
1409   else if (WINDOWP (window))
1410     {
1411       CHECK_LIVE_WINDOW (window);
1412       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1413     }
1414   else
1415     CHECK_WINDOW (window);
1417   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1418   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1420   title = Fcar (contents);
1421   CHECK_STRING (title);
1423   if (NILP (Fcar (Fcdr (contents))))
1424     /* No buttons specified, add an "Ok" button so users can pop down
1425        the dialog.  */
1426     contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1428   BLOCK_INPUT;
1429   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1430                                            isQuestion: isQ];
1431   {
1432     int specpdl_count = SPECPDL_INDEX ();
1433     record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1434     popup_activated_flag = 1;
1435     tem = [dialog runDialogAt: p];
1436     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1437   }
1438   UNBLOCK_INPUT;
1440   return tem;
1444 /* ==========================================================================
1446     Popup Dialog: class implementation
1448    ========================================================================== */
1450 @interface FlippedView : NSView
1453 @end
1455 @implementation FlippedView
1456 - (BOOL)isFlipped
1458   return YES;
1460 @end
1462 @implementation EmacsDialogPanel
1464 #define SPACER          8.0
1465 #define ICONSIZE        64.0
1466 #define TEXTHEIGHT      20.0
1467 #define MINCELLWIDTH    90.0
1469 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1470               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1472   NSSize spacing = {SPACER, SPACER};
1473   NSRect area;
1474   char this_cmd_name[80];
1475   id cell;
1476   static NSImageView *imgView;
1477   static FlippedView *contentView;
1479   if (imgView == nil)
1480     {
1481       NSImage *img;
1482       area.origin.x   = 3*SPACER;
1483       area.origin.y   = 2*SPACER;
1484       area.size.width = ICONSIZE;
1485       area.size.height= ICONSIZE;
1486       img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1487       [img setScalesWhenResized: YES];
1488       [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1489       imgView = [[NSImageView alloc] initWithFrame: area];
1490       [imgView setImage: img];
1491       [imgView setEditable: NO];
1492       [img release];
1493     }
1495   aStyle = NSTitledWindowMask;
1496   flag = YES;
1497   rows = 0;
1498   cols = 1;
1499   [super initWithContentRect: contentRect styleMask: aStyle
1500                      backing: backingType defer: flag];
1501   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1502   [self setContentView: contentView];
1504   [[self contentView] setAutoresizesSubviews: YES];
1506   [[self contentView] addSubview: imgView];
1507   [self setTitle: @""];
1509   area.origin.x   += ICONSIZE+2*SPACER;
1510 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1511   area.size.width = 400;
1512   area.size.height= TEXTHEIGHT;
1513   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1514   [[self contentView] addSubview: command];
1515   [command setStringValue: ns_app_name];
1516   [command setDrawsBackground: NO];
1517   [command setBezeled: NO];
1518   [command setSelectable: NO];
1519   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1521 /*  area.origin.x   = ICONSIZE+2*SPACER;
1522   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1523   area.size.width = 400;
1524   area.size.height= 2;
1525   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1526   [[self contentView] addSubview: tem];
1527   [tem setTitlePosition: NSNoTitle];
1528   [tem setAutoresizingMask: NSViewWidthSizable];*/
1530 /*  area.origin.x = ICONSIZE+2*SPACER; */
1531   area.origin.y += TEXTHEIGHT+SPACER;
1532   area.size.width = 400;
1533   area.size.height= TEXTHEIGHT;
1534   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1535   [[self contentView] addSubview: title];
1536   [title setDrawsBackground: NO];
1537   [title setBezeled: NO];
1538   [title setSelectable: NO];
1539   [title setFont: [NSFont systemFontOfSize: 11.0]];
1541   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1542   [cell setBordered: NO];
1543   [cell setEnabled: NO];
1544   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1545   [cell setBezelStyle: NSRoundedBezelStyle];
1547   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1548                                       mode: NSHighlightModeMatrix
1549                                  prototype: cell
1550                               numberOfRows: 0
1551                            numberOfColumns: 1];
1552   [[self contentView] addSubview: matrix];
1553   [matrix release];
1554   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1555                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1556   [matrix setIntercellSpacing: spacing];
1558   [self setOneShot: YES];
1559   [self setReleasedWhenClosed: YES];
1560   [self setHidesOnDeactivate: YES];
1561   return self;
1565 - (BOOL)windowShouldClose: (id)sender
1567   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1568   return NO;
1572 void process_dialog (id window, Lisp_Object list)
1574   Lisp_Object item;
1575   int row = 0;
1577   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1578     {
1579       item = XCAR (list);
1580       if (XTYPE (item) == Lisp_String)
1581         {
1582           [window addString: SDATA (item) row: row++];
1583         }
1584       else if (XTYPE (item) == Lisp_Cons)
1585         {
1586           [window addButton: SDATA (XCAR (item))
1587                       value: XCDR (item) row: row++];
1588         }
1589       else if (NILP (item))
1590         {
1591           [window addSplit];
1592           row = 0;
1593         }
1594     }
1598 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1600   id cell;
1602   if (row >= rows)
1603     {
1604       [matrix addRow];
1605       rows++;
1606     }
1607   cell = [matrix cellAtRow: row column: cols-1];
1608   [cell setTarget: self];
1609   [cell setAction: @selector (clicked: )];
1610   [cell setTitle: [NSString stringWithUTF8String: str]];
1611   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1612   [cell setBordered: YES];
1613   [cell setEnabled: YES];
1615   return self;
1619 - addString: (char *)str row: (int)row
1621   id cell;
1623   if (row >= rows)
1624     {
1625       [matrix addRow];
1626       rows++;
1627     }
1628   cell = [matrix cellAtRow: row column: cols-1];
1629   [cell setTitle: [NSString stringWithUTF8String: str]];
1630   [cell setBordered: YES];
1631   [cell setEnabled: NO];
1633   return self;
1637 - addSplit
1639   [matrix addColumn];
1640   cols++;
1641   return self;
1645 - clicked: sender
1647   NSArray *sellist = nil;
1648   EMACS_INT seltag;
1650   sellist = [sender selectedCells];
1651   if ([sellist count]<1)
1652     return self;
1654   seltag = [[sellist objectAtIndex: 0] tag];
1655   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1656     [NSApp stopModalWithCode: seltag];
1657   return self;
1661 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1663   Lisp_Object head;
1664   [super init];
1666   if (XTYPE (contents) == Lisp_Cons)
1667     {
1668       head = Fcar (contents);
1669       process_dialog (self, Fcdr (contents));
1670     }
1671   else
1672     head = contents;
1674   if (XTYPE (head) == Lisp_String)
1675       [title setStringValue:
1676                  [NSString stringWithUTF8String: SDATA (head)]];
1677   else if (isQ == YES)
1678       [title setStringValue: @"Question"];
1679   else
1680       [title setStringValue: @"Information"];
1682   {
1683     int i;
1684     NSRect r, s, t;
1686     if (cols == 1 && rows > 1)  /* Never told where to split */
1687       {
1688         [matrix addColumn];
1689         for (i = 0; i<rows/2; i++)
1690           {
1691             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1692                       atRow: i column: 1];
1693             [matrix removeRow: (rows+1)/2];
1694           }
1695       }
1697     [matrix sizeToFit];
1698     {
1699       NSSize csize = [matrix cellSize];
1700       if (csize.width < MINCELLWIDTH)
1701         {
1702           csize.width = MINCELLWIDTH;
1703           [matrix setCellSize: csize];
1704           [matrix sizeToCells];
1705         }
1706     }
1708     [title sizeToFit];
1709     [command sizeToFit];
1711     t = [matrix frame];
1712     r = [title frame];
1713     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1714       {
1715         t.origin.x   = r.origin.x;
1716         t.size.width = r.size.width;
1717       }
1718     r = [command frame];
1719     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1720       {
1721         t.origin.x   = r.origin.x;
1722         t.size.width = r.size.width;
1723       }
1725     r = [self frame];
1726     s = [(NSView *)[self contentView] frame];
1727     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1728     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1729     [self setFrame: r display: NO];
1730   }
1732   return self;
1736 - (void)dealloc
1738   { [super dealloc]; return; };
1742 - (Lisp_Object)runDialogAt: (NSPoint)p
1744   NSInteger ret;
1746   /* initiate a session that will be ended by pop_down_menu */
1747   popupSession = [NSApp beginModalSessionForWindow: self];
1748   while (popup_activated_flag
1749          && (ret = [NSApp runModalSession: popupSession])
1750               == NSRunContinuesResponse)
1751     {
1752       /* Run this for timers.el, indep of atimers; might not return.
1753          TODO: use return value to avoid calling every iteration. */
1754       timer_check ();
1755       [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1756     }
1758   {                             /* FIXME: BIG UGLY HACK!!! */
1759       Lisp_Object tmp;
1760       *(EMACS_INT*)(&tmp) = ret;
1761       return tmp;
1762   }
1765 @end
1768 /* ==========================================================================
1770     Lisp definitions
1772    ========================================================================== */
1774 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1775        doc: /* Cause the NS menu to be re-calculated.  */)
1776      (void)
1778   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1779   return Qnil;
1783 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1784        doc: /* Pop up a dialog box and return user's selection.
1785 POSITION specifies which frame to use.
1786 This is normally a mouse button event or a window or frame.
1787 If POSITION is t, it means to use the frame the mouse is on.
1788 The dialog box appears in the middle of the specified frame.
1790 CONTENTS specifies the alternatives to display in the dialog box.
1791 It is a list of the form (DIALOG ITEM1 ITEM2...).
1792 Each ITEM is a cons cell (STRING . VALUE).
1793 The return value is VALUE from the chosen item.
1795 An ITEM may also be just a string--that makes a nonselectable item.
1796 An ITEM may also be nil--that means to put all preceding items
1797 on the left of the dialog box and all following items on the right.
1798 \(By default, approximately half appear on each side.)
1800 If HEADER is non-nil, the frame title for the box is "Information",
1801 otherwise it is "Question".
1803 If the user gets rid of the dialog box without making a valid choice,
1804 for instance using the window manager, then this produces a quit and
1805 `x-popup-dialog' does not return.  */)
1806      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1808   return ns_popup_dialog (position, contents, header);
1811 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1812        doc: /* Return t if a menu or popup dialog is active.  */)
1813      (void)
1815   return popup_activated () ? Qt : Qnil;
1818 /* ==========================================================================
1820     Lisp interface declaration
1822    ========================================================================== */
1824 void
1825 syms_of_nsmenu (void)
1827 #ifndef NS_IMPL_COCOA
1828   /* Don't know how to keep track of this in Next/Open/Gnustep.  Always
1829      update menus there.  */
1830   trackingMenu = 1;
1831 #endif
1832   defsubr (&Sx_popup_dialog);
1833   defsubr (&Sns_reset_menu);
1834   defsubr (&Smenu_or_popup_active_p);
1836   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1837   staticpro (&Qdebug_on_next_call);