notes/copyright: Report status more accurately for non-GPL files.
[emacs.git] / src / nsmenu.m
blob95f651f66903ead554922850bd8ddcccb0df4610
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007, 2008, 2009, 2010 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 Voverriding_local_map, Voverriding_local_map_menu_flag,
69                    Qoverriding_local_map, Qoverriding_terminal_local_map;
71 extern long context_menu_value;
72 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
74 /* Nonzero means a menu is currently active.  */
75 static int popup_activated_flag;
76 static NSModalSession popupSession;
78 /* NOTE: toolbar implementation is at end,
79   following complete menu implementation. */
82 /* ==========================================================================
84     Menu: Externally-called functions
86    ========================================================================== */
89 /* FIXME: not currently used, but should normalize with other terms. */
90 void
91 x_activate_menubar (struct frame *f)
93     fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
97 /* Supposed to discard menubar and free storage.  Since we share the
98    menubar among frames and update its context for the focused window,
99    there is nothing to do here. */
100 void
101 free_frame_menubar (struct frame *f)
103   return;
108 popup_activated (void)
110   return popup_activated_flag;
114 /* --------------------------------------------------------------------------
115     Update menubar.  Three cases:
116     1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
117        just top-level menu strings (OS X), or goto case (2) (GNUstep).
118     2) deep_p = 1, submenu = nil: Recompute all submenus.
119     3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
120    -------------------------------------------------------------------------- */
121 void
122 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
124   NSAutoreleasePool *pool;
125   id menu = [NSApp mainMenu];
126   static EmacsMenu *last_submenu = nil;
127   BOOL needsSet = NO;
128   const char *submenuTitle = [[submenu title] UTF8String];
129   extern int waiting_for_input;
130   int owfi;
131   Lisp_Object items;
132   widget_value *wv, *first_wv, *prev_wv = 0;
133   int i;
135 #if NSMENUPROFILE
136   struct timeb tb;
137   long t;
138 #endif
140   NSTRACE (set_frame_menubar);
142   if (f != SELECTED_FRAME ())
143       return;
144   XSETFRAME (Vmenu_updating_frame, f);
145 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
147   BLOCK_INPUT;
148   pool = [[NSAutoreleasePool alloc] init];
150   /* Menu may have been created automatically; if so, discard it. */
151   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
152     {
153       [menu release];
154       menu = nil;
155     }
157   if (menu == nil)
158     {
159       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
160       needsSet = YES;
161     }
162   else
163     {  /* close up anything on there */
164       id attMenu = [menu attachedMenu];
165       if (attMenu != nil)
166         [attMenu close];
167     }
169 #if NSMENUPROFILE
170   ftime (&tb);
171   t = -(1000*tb.time+tb.millitm);
172 #endif
174 #ifdef NS_IMPL_GNUSTEP
175   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
176 #endif
178   if (deep_p)
179     {
180       /* Fully parse one or more of the submenus. */
181       int n = 0;
182       int *submenu_start, *submenu_end;
183       int *submenu_top_level_items, *submenu_n_panes;
184       struct buffer *prev = current_buffer;
185       Lisp_Object buffer;
186       int specpdl_count = SPECPDL_INDEX ();
187       int previous_menu_items_used = f->menu_bar_items_used;
188       Lisp_Object *previous_items
189         = (Lisp_Object *) alloca (previous_menu_items_used
190                                   * sizeof (Lisp_Object));
192       /* lisp preliminaries */
193       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
194       specbind (Qinhibit_quit, Qt);
195       specbind (Qdebug_on_next_call, Qnil);
196       record_unwind_save_match_data ();
197       if (NILP (Voverriding_local_map_menu_flag))
198         {
199           specbind (Qoverriding_terminal_local_map, Qnil);
200           specbind (Qoverriding_local_map, Qnil);
201         }
202       set_buffer_internal_1 (XBUFFER (buffer));
204       /* TODO: for some reason this is not needed in other terms,
205            but some menu updates call Info-extract-pointer which causes
206            abort-on-error if waiting-for-input.  Needs further investigation. */
207       owfi = waiting_for_input;
208       waiting_for_input = 0;
210       /* lucid hook and possible reset */
211       safe_run_hooks (Qactivate_menubar_hook);
212       if (! NILP (Vlucid_menu_bar_dirty_flag))
213         call0 (Qrecompute_lucid_menubar);
214       safe_run_hooks (Qmenu_bar_update_hook);
215       FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
217       /* Now ready to go */
218       items = FRAME_MENU_BAR_ITEMS (f);
220       /* Save the frame's previous menu bar contents data */
221       if (previous_menu_items_used)
222         memcpy (previous_items, XVECTOR (f->menu_bar_vector)->contents,
223                 previous_menu_items_used * sizeof (Lisp_Object));
225       /* parse stage 1: extract from lisp */
226       save_menu_items ();
228       menu_items = f->menu_bar_vector;
229       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
230       submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
231       submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
232       submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
233       submenu_top_level_items
234         = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
235       init_menu_items ();
236       for (i = 0; i < XVECTOR (items)->size; i += 4)
237         {
238           Lisp_Object key, string, maps;
240           key = XVECTOR (items)->contents[i];
241           string = XVECTOR (items)->contents[i + 1];
242           maps = XVECTOR (items)->contents[i + 2];
243           if (NILP (string))
244             break;
246           /* FIXME: we'd like to only parse the needed submenu, but this
247                was causing crashes in the _common parsing code.. need to make
248                sure proper initialization done.. */
249 /*        if (submenu && strcmp (submenuTitle, SDATA (string)))
250              continue; */
252           submenu_start[i] = menu_items_used;
254           menu_items_n_panes = 0;
255           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
256           submenu_n_panes[i] = menu_items_n_panes;
257           submenu_end[i] = menu_items_used;
258           n++;
259         }
261       finish_menu_items ();
262       waiting_for_input = owfi;
265       if (submenu && n == 0)
266         {
267           /* should have found a menu for this one but didn't */
268           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
269                   submenuTitle);
270           discard_menu_items ();
271           unbind_to (specpdl_count, Qnil);
272           [pool release];
273           UNBLOCK_INPUT;
274           return;
275         }
277       /* parse stage 2: insert into lucid 'widget_value' structures
278          [comments in other terms say not to evaluate lisp code here] */
279       wv = xmalloc_widget_value ();
280       wv->name = "menubar";
281       wv->value = 0;
282       wv->enabled = 1;
283       wv->button_type = BUTTON_TYPE_NONE;
284       wv->help = Qnil;
285       first_wv = wv;
287       for (i = 0; i < 4*n; i += 4)
288         {
289           menu_items_n_panes = submenu_n_panes[i];
290           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
291                                       submenu_top_level_items[i]);
292           if (prev_wv)
293             prev_wv->next = wv;
294           else
295             first_wv->contents = wv;
296           /* Don't set wv->name here; GC during the loop might relocate it.  */
297           wv->enabled = 1;
298           wv->button_type = BUTTON_TYPE_NONE;
299           prev_wv = wv;
300         }
302       set_buffer_internal_1 (prev);
304       /* Compare the new menu items with previous, and leave off if no change */
305       /* FIXME: following other terms here, but seems like this should be
306            done before parse stage 2 above, since its results aren't used */
307       if (previous_menu_items_used
308           && (!submenu || (submenu && submenu == last_submenu))
309           && menu_items_used == previous_menu_items_used)
310         {
311           for (i = 0; i < previous_menu_items_used; i++)
312             /* FIXME: this ALWAYS fails on Buffers menu items.. something
313                  about their strings causes them to change every time, so we
314                  double-check failures */
315             if (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i]))
316               if (!(STRINGP (previous_items[i])
317                     && STRINGP (XVECTOR (menu_items)->contents[i])
318                     && !strcmp (SDATA (previous_items[i]),
319                                SDATA (XVECTOR (menu_items)->contents[i]))))
320                   break;
321           if (i == previous_menu_items_used)
322             {
323               /* No change.. */
325 #if NSMENUPROFILE
326               ftime (&tb);
327               t += 1000*tb.time+tb.millitm;
328               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
329 #endif
331               free_menubar_widget_value_tree (first_wv);
332               discard_menu_items ();
333               unbind_to (specpdl_count, Qnil);
334               [pool release];
335               UNBLOCK_INPUT;
336               return;
337             }
338         }
339       /* The menu items are different, so store them in the frame */
340       /* FIXME: this is not correct for single-submenu case */
341       f->menu_bar_vector = menu_items;
342       f->menu_bar_items_used = menu_items_used;
344       /* Calls restore_menu_items, etc., as they were outside */
345       unbind_to (specpdl_count, Qnil);
347       /* Parse stage 2a: now GC cannot happen during the lifetime of the
348          widget_value, so it's safe to store data from a Lisp_String */
349       wv = first_wv->contents;
350       for (i = 0; i < XVECTOR (items)->size; i += 4)
351         {
352           Lisp_Object string;
353           string = XVECTOR (items)->contents[i + 1];
354           if (NILP (string))
355             break;
356 /*           if (submenu && strcmp (submenuTitle, SDATA (string)))
357                continue; */
359           wv->name = (char *) SDATA (string);
360           update_submenu_strings (wv->contents);
361           wv = wv->next;
362         }
364       /* Now, update the NS menu; if we have a submenu, use that, otherwise
365          create a new menu for each sub and fill it. */
366       if (submenu)
367         {
368           for (wv = first_wv->contents; wv; wv = wv->next)
369             {
370               if (!strcmp (submenuTitle, wv->name))
371                 {
372                   [submenu fillWithWidgetValue: wv->contents];
373                   last_submenu = submenu;
374                   break;
375                 }
376             }
377         }
378       else
379         {
380           [menu fillWithWidgetValue: first_wv->contents];
381         }
383     }
384   else
385     {
386       static int n_previous_strings = 0;
387       static char previous_strings[100][10];
388       static struct frame *last_f = NULL;
389       int n;
390       Lisp_Object string;
392       wv = xmalloc_widget_value ();
393       wv->name = "menubar";
394       wv->value = 0;
395       wv->enabled = 1;
396       wv->button_type = BUTTON_TYPE_NONE;
397       wv->help = Qnil;
398       first_wv = wv;
400       /* Make widget-value tree w/ just the top level menu bar strings */
401       items = FRAME_MENU_BAR_ITEMS (f);
402       if (NILP (items))
403         {
404           [pool release];
405           UNBLOCK_INPUT;
406           return;
407         }
410       /* check if no change.. this mechanism is a bit rough, but ready */
411       n = XVECTOR (items)->size / 4;
412       if (f == last_f && n_previous_strings == n)
413         {
414           for (i = 0; i<n; i++)
415             {
416               string = AREF (items, 4*i+1);
418               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
419                 continue;
420               if (NILP (string))
421                 if (previous_strings[i][0])
422                   break;
423               else
424                 continue;
425               if (strncmp (previous_strings[i], SDATA (string), 10))
426                 break;
427             }
429           if (i == n)
430             {
431               [pool release];
432               UNBLOCK_INPUT;
433               return;
434             }
435         }
437       [menu clear];
438       for (i = 0; i < XVECTOR (items)->size; i += 4)
439         {
440           string = XVECTOR (items)->contents[i + 1];
441           if (NILP (string))
442             break;
444           if (n < 100)
445             strncpy (previous_strings[i/4], SDATA (string), 10);
447           wv = xmalloc_widget_value ();
448           wv->name = (char *) SDATA (string);
449           wv->value = 0;
450           wv->enabled = 1;
451           wv->button_type = BUTTON_TYPE_NONE;
452           wv->help = Qnil;
453           wv->call_data = (void *) (EMACS_INT) (-1);
455 #ifdef NS_IMPL_COCOA
456           /* we'll update the real copy under app menu when time comes */
457           if (!strcmp ("Services", wv->name))
458             {
459               /* but we need to make sure it will update on demand */
460               [svcsMenu setFrame: f];
461               [svcsMenu setDelegate: svcsMenu];
462             }
463           else
464 #endif
465           [menu addSubmenuWithTitle: wv->name forFrame: f];
467           if (prev_wv)
468             prev_wv->next = wv;
469           else
470             first_wv->contents = wv;
471           prev_wv = wv;
472         }
474       last_f = f;
475       if (n < 100)
476         n_previous_strings = n;
477       else
478         n_previous_strings = 0;
480     }
481   free_menubar_widget_value_tree (first_wv);
484 #if NSMENUPROFILE
485   ftime (&tb);
486   t += 1000*tb.time+tb.millitm;
487   fprintf (stderr, "Menu update took %ld msec.\n", t);
488 #endif
490   /* set main menu */
491   if (needsSet)
492     [NSApp setMainMenu: menu];
494   [pool release];
495   UNBLOCK_INPUT;
500 /* Main emacs core entry point for menubar menus: called to indicate that the
501    frame's menus have changed, and the *step representation should be updated
502    from Lisp. */
503 void
504 set_frame_menubar (struct frame *f, int first_time, int deep_p)
506   ns_update_menubar (f, deep_p, nil);
510 /* ==========================================================================
512     Menu: class implementation
514    ========================================================================== */
517 /* Menu that can define itself from Emacs "widget_value"s and will lazily
518    update itself when user clicked.  Based on Carbon/AppKit implementation
519    by Yamamoto Mitsuharu. */
520 @implementation EmacsMenu
522 /* override designated initializer */
523 - initWithTitle: (NSString *)title
525   if (self = [super initWithTitle: title])
526     [self setAutoenablesItems: NO];
527   return self;
531 /* used for top-level */
532 - initWithTitle: (NSString *)title frame: (struct frame *)f
534   [self initWithTitle: title];
535   frame = f;
536 #ifdef NS_IMPL_COCOA
537   [self setDelegate: self];
538 #endif
539   return self;
543 - (void)setFrame: (struct frame *)f
545   frame = f;
549 /* delegate method called when a submenu is being opened: run a 'deep' call
550    to set_frame_menubar */
551 - (void)menuNeedsUpdate: (NSMenu *)menu
553   NSEvent *event;
554   if (!FRAME_LIVE_P (frame))
555     return;
556   event = [[FRAME_NS_VIEW (frame) window] currentEvent];
557   /* HACK: Cocoa/Carbon will request update on every keystroke
558      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
559      since key equivalents are handled through emacs.
560      On Leopard, even keystroke events generate SystemDefined events, but
561      their subtype is 8. */
562   if ([event type] != NSSystemDefined || [event subtype] == 8
563       /* Also, don't try this if from an event picked up asynchronously,
564          as lots of lisp evaluation happens in ns_update_menubar. */
565       || handling_signal != 0)
566     return;
567 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
568   ns_update_menubar (frame, 1, self);
572 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
574   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
575       && FRAME_NS_VIEW (SELECTED_FRAME ()))
576     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
577   return YES;
581 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
582    into an accelerator string.  We are only able to display a single character
583    for an accelerator, together with an optional modifier combination.  (Under
584    Carbon more control was possible, but in Cocoa multi-char strings passed to
585    NSMenuItem get ignored.  For now we try to display a super-single letter
586    combo, and return the others as strings to be appended to the item title.
587    (This is signaled by setting keyEquivModMask to 0 for now.) */
588 -(NSString *)parseKeyEquiv: (const char *)key
590   const char *tpos = key;
591   keyEquivModMask = NSCommandKeyMask;
593   if (!key || !strlen (key))
594     return @"";
595   
596   while (*tpos == ' ' || *tpos == '(')
597     tpos++;
598   if ((*tpos == 's') && (*(tpos+1) == '-'))
599     {
600       return [NSString stringWithFormat: @"%c", tpos[2]];
601     }
602   keyEquivModMask = 0; /* signal */
603   return [NSString stringWithUTF8String: tpos];
607 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
609   NSMenuItem *item;
610   widget_value *wv = (widget_value *)wvptr;
612   if (menu_separator_name_p (wv->name))
613     {
614       item = [NSMenuItem separatorItem];
615       [self addItem: item];
616     }
617   else
618     {
619       NSString *title, *keyEq;
620       title = [NSString stringWithUTF8String: wv->name];
621       if (title == nil)
622         title = @"< ? >";  /* (get out in the open so we know about it) */
624       keyEq = [self parseKeyEquiv: wv->key];
625 #ifdef NS_IMPL_COCOA
626       /* OS X just ignores modifier strings longer than one character */
627       if (keyEquivModMask == 0)
628         title = [title stringByAppendingFormat: @" (%@)", keyEq];
629 #endif
631       item = [self addItemWithTitle: (NSString *)title
632                              action: @selector (menuDown:)
633                       keyEquivalent: keyEq];
634       [item setKeyEquivalentModifierMask: keyEquivModMask];
636       [item setEnabled: wv->enabled];
638       /* Draw radio buttons and tickboxes */
639       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
640                            wv->button_type == BUTTON_TYPE_RADIO))
641         [item setState: NSOnState];
642       else
643         [item setState: NSOffState];
645       [item setTag: (NSInteger)wv->call_data];
646     }
648   return item;
652 /* convenience */
653 -(void)clear
655   int n;
656   
657   for (n = [self numberOfItems]-1; n >= 0; n--)
658     {
659       NSMenuItem *item = [self itemAtIndex: n];
660       NSString *title = [item title];
661       if (([title length] == 0  /* OSX 10.5 */
662            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
663            || [@"Apple" isEqualToString: title]) /* older */
664           && ![item isSeparatorItem])
665         continue;
666       [self removeItemAtIndex: n];
667     }
671 - (void)fillWithWidgetValue: (void *)wvptr
673   widget_value *wv = (widget_value *)wvptr;
675   /* clear existing contents */
676   [self setMenuChangedMessagesEnabled: NO];
677   [self clear];
679   /* add new contents */
680   for (; wv != NULL; wv = wv->next)
681     {
682       NSMenuItem *item = [self addItemWithWidgetValue: wv];
684       if (wv->contents)
685         {
686           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
688           [self setSubmenu: submenu forItem: item];
689           [submenu fillWithWidgetValue: wv->contents];
690           [submenu release];
691           [item setAction: nil];
692         }
693     }
695   [self setMenuChangedMessagesEnabled: YES];
696 #ifdef NS_IMPL_GNUSTEP
697   if ([[self window] isVisible])
698     [self sizeToFit];
699 #else
700   if ([self supermenu] == nil)
701     [self sizeToFit];
702 #endif
706 /* adds an empty submenu and returns it */
707 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
709   NSString *titleStr = [NSString stringWithUTF8String: title];
710   NSMenuItem *item = [self addItemWithTitle: titleStr
711                                      action: nil /*@selector (menuDown:) */
712                               keyEquivalent: @""];
713   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
714   [self setSubmenu: submenu forItem: item];
715   [submenu release];
716   return submenu;
719 /* run a menu in popup mode */
720 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
721                  keymaps: (int)keymaps
723   EmacsView *view = FRAME_NS_VIEW (f);
724   NSEvent *e, *event;
725   long retVal;
727 /*   p = [view convertPoint:p fromView: nil]; */
728   p.y = NSHeight ([view frame]) - p.y;
729   e = [[view window] currentEvent];
730    event = [NSEvent mouseEventWithType: NSRightMouseDown
731                               location: p
732                          modifierFlags: 0
733                              timestamp: [e timestamp]
734                           windowNumber: [[view window] windowNumber]
735                                context: [e context]
736                            eventNumber: 0/*[e eventNumber] */
737                             clickCount: 1
738                               pressure: 0];
740   context_menu_value = -1;
741   [NSMenu popUpContextMenu: self withEvent: event forView: view];
742   retVal = context_menu_value;
743   context_menu_value = 0;
744   return retVal > 0
745       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
746       : Qnil;
749 @end  /* EmacsMenu */
753 /* ==========================================================================
755     Context Menu: implementing functions
757    ========================================================================== */
759 Lisp_Object
760 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
761               Lisp_Object title, const char **error)
763   EmacsMenu *pmenu;
764   NSPoint p;
765   Lisp_Object window, tem, keymap;
766   int specpdl_count = SPECPDL_INDEX ();
767   widget_value *wv, *first_wv = 0;
769   p.x = x; p.y = y;
771   /* now parse stage 2 as in ns_update_menubar */
772   wv = xmalloc_widget_value ();
773   wv->name = "contextmenu";
774   wv->value = 0;
775   wv->enabled = 1;
776   wv->button_type = BUTTON_TYPE_NONE;
777   wv->help = Qnil;
778   first_wv = wv;
780 #if 0
781   /* FIXME: a couple of one-line differences prevent reuse */
782   wv = digest_single_submenu (0, menu_items_used, Qnil);
783 #else
784   {
785   widget_value *save_wv = 0, *prev_wv = 0;
786   widget_value **submenu_stack
787     = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
788 /*   Lisp_Object *subprefix_stack
789        = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
790   int submenu_depth = 0;
791   int first_pane = 1;
792   int i;
794   /* Loop over all panes and items, filling in the tree.  */
795   i = 0;
796   while (i < menu_items_used)
797     {
798       if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
799         {
800           submenu_stack[submenu_depth++] = save_wv;
801           save_wv = prev_wv;
802           prev_wv = 0;
803           first_pane = 1;
804           i++;
805         }
806       else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
807         {
808           prev_wv = save_wv;
809           save_wv = submenu_stack[--submenu_depth];
810           first_pane = 0;
811           i++;
812         }
813       else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
814                && submenu_depth != 0)
815         i += MENU_ITEMS_PANE_LENGTH;
816       /* Ignore a nil in the item list.
817          It's meaningful only for dialog boxes.  */
818       else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
819         i += 1;
820       else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
821         {
822           /* Create a new pane.  */
823           Lisp_Object pane_name, prefix;
824           const char *pane_string;
826           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
827           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
829 #ifndef HAVE_MULTILINGUAL_MENU
830           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
831             {
832               pane_name = ENCODE_MENU_STRING (pane_name);
833               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
834             }
835 #endif
836           pane_string = (NILP (pane_name)
837                          ? "" : (char *) SDATA (pane_name));
838           /* If there is just one top-level pane, put all its items directly
839              under the top-level menu.  */
840           if (menu_items_n_panes == 1)
841             pane_string = "";
843           /* If the pane has a meaningful name,
844              make the pane a top-level menu item
845              with its items as a submenu beneath it.  */
846           if (!keymaps && strcmp (pane_string, ""))
847             {
848               wv = xmalloc_widget_value ();
849               if (save_wv)
850                 save_wv->next = wv;
851               else
852                 first_wv->contents = wv;
853               wv->name = pane_string;
854               if (keymaps && !NILP (prefix))
855                 wv->name++;
856               wv->value = 0;
857               wv->enabled = 1;
858               wv->button_type = BUTTON_TYPE_NONE;
859               wv->help = Qnil;
860               save_wv = wv;
861               prev_wv = 0;
862             }
863           else if (first_pane)
864             {
865               save_wv = wv;
866               prev_wv = 0;
867             }
868           first_pane = 0;
869           i += MENU_ITEMS_PANE_LENGTH;
870         }
871       else
872         {
873           /* Create a new item within current pane.  */
874           Lisp_Object item_name, enable, descrip, def, type, selected, help;
875           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
876           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
877           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
878           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
879           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
880           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
881           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
883 #ifndef HAVE_MULTILINGUAL_MENU
884           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
885             {
886               item_name = ENCODE_MENU_STRING (item_name);
887               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
888             }
890           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
891             {
892               descrip = ENCODE_MENU_STRING (descrip);
893               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
894             }
895 #endif /* not HAVE_MULTILINGUAL_MENU */
897           wv = xmalloc_widget_value ();
898           if (prev_wv)
899             prev_wv->next = wv;
900           else
901             save_wv->contents = wv;
902           wv->name = (char *) SDATA (item_name);
903           if (!NILP (descrip))
904             wv->key = (char *) SDATA (descrip);
905           wv->value = 0;
906           /* If this item has a null value,
907              make the call_data null so that it won't display a box
908              when the mouse is on it.  */
909           wv->call_data
910               = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
911           wv->enabled = !NILP (enable);
913           if (NILP (type))
914             wv->button_type = BUTTON_TYPE_NONE;
915           else if (EQ (type, QCtoggle))
916             wv->button_type = BUTTON_TYPE_TOGGLE;
917           else if (EQ (type, QCradio))
918             wv->button_type = BUTTON_TYPE_RADIO;
919           else
920             abort ();
922           wv->selected = !NILP (selected);
924           if (! STRINGP (help))
925             help = Qnil;
927           wv->help = help;
929           prev_wv = wv;
931           i += MENU_ITEMS_ITEM_LENGTH;
932         }
933     }
934   }
935 #endif
937   if (!NILP (title))
938     {
939       widget_value *wv_title = xmalloc_widget_value ();
940       widget_value *wv_sep = xmalloc_widget_value ();
942       /* Maybe replace this separator with a bitmap or owner-draw item
943          so that it looks better.  Having two separators looks odd.  */
944       wv_sep->name = "--";
945       wv_sep->next = first_wv->contents;
946       wv_sep->help = Qnil;
948 #ifndef HAVE_MULTILINGUAL_MENU
949       if (STRING_MULTIBYTE (title))
950         title = ENCODE_MENU_STRING (title);
951 #endif
953       wv_title->name = (char *) SDATA (title);
954       wv_title->enabled = NO;
955       wv_title->button_type = BUTTON_TYPE_NONE;
956       wv_title->help = Qnil;
957       wv_title->next = wv_sep;
958       first_wv->contents = wv_title;
959     }
961   pmenu = [[EmacsMenu alloc] initWithTitle:
962                                [NSString stringWithUTF8String: SDATA (title)]];
963   [pmenu fillWithWidgetValue: first_wv->contents];
964   free_menubar_widget_value_tree (first_wv);
965   unbind_to (specpdl_count, Qnil);
967   popup_activated_flag = 1;
968   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
969   popup_activated_flag = 0;
970   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
972   return tem;
976 /* ==========================================================================
978     Toolbar: externally-called functions
980    ========================================================================== */
982 void
983 free_frame_tool_bar (FRAME_PTR f)
984 /* --------------------------------------------------------------------------
985     Under NS we just hide the toolbar until it might be needed again.
986    -------------------------------------------------------------------------- */
988   BLOCK_INPUT;
989   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
990   FRAME_TOOLBAR_HEIGHT (f) = 0;
991   UNBLOCK_INPUT;
994 void
995 update_frame_tool_bar (FRAME_PTR f)
996 /* --------------------------------------------------------------------------
997     Update toolbar contents
998    -------------------------------------------------------------------------- */
1000   int i;
1001   EmacsView *view = FRAME_NS_VIEW (f);
1002   NSWindow *window = [view window];
1003   EmacsToolbar *toolbar = [view toolbar];
1005   BLOCK_INPUT;
1006   [toolbar clearActive];
1008   /* update EmacsToolbar as in GtkUtils, build items list */
1009   for (i = 0; i < f->n_tool_bar_items; ++i)
1010     {
1011 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1012                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1014       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1015       BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1016       int idx;
1017       int img_id;
1018       struct image *img;
1019       Lisp_Object image;
1020       Lisp_Object helpObj;
1021       const char *helpText;
1023       /* If image is a vector, choose the image according to the
1024          button state.  */
1025       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1026       if (VECTORP (image))
1027         {
1028           /* NS toolbar auto-computes disabled and selected images */
1029           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1030           xassert (ASIZE (image) >= idx);
1031           image = AREF (image, idx);
1032         }
1033       else
1034         {
1035           idx = -1;
1036         }
1037       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1038       if (NILP (helpObj))
1039         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1040       helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1042       /* Ignore invalid image specifications.  */
1043       if (!valid_image_p (image))
1044         {
1045           /* Don't log anything, GNUS makes invalid images all the time.  */
1046           continue;
1047         }
1049       img_id = lookup_image (f, image);
1050       img = IMAGE_FROM_ID (f, img_id);
1051       prepare_image_for_display (f, img);
1053       if (img->load_failed_p || img->pixmap == nil)
1054         {
1055           NSLog (@"Could not prepare toolbar image for display.");
1056           continue;
1057         }
1059       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1060                                enabled: enabled_p];
1061 #undef TOOLPROP
1062     }
1064   if (![toolbar isVisible])
1065       [toolbar setVisible: YES];
1067   if ([toolbar changed])
1068     {
1069       /* inform app that toolbar has changed */
1070       NSDictionary *dict = [toolbar configurationDictionary];
1071       NSMutableDictionary *newDict = [dict mutableCopy];
1072       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1073       NSObject *key;
1074       while ((key = [keys nextObject]) != nil)
1075         {
1076           NSObject *val = [dict objectForKey: key];
1077           if ([val isKindOfClass: [NSArray class]])
1078             {
1079               [newDict setObject:
1080                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1081                           forKey: key];
1082               break;
1083             }
1084         }
1085       [toolbar setConfigurationFromDictionary: newDict];
1086       [newDict release];
1087     }
1089   FRAME_TOOLBAR_HEIGHT (f) =
1090     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1091     - FRAME_NS_TITLEBAR_HEIGHT (f);
1092   UNBLOCK_INPUT;
1096 /* ==========================================================================
1098     Toolbar: class implementation
1100    ========================================================================== */
1102 @implementation EmacsToolbar
1104 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1106   self = [super initWithIdentifier: identifier];
1107   emacsView = view;
1108   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1109   [self setSizeMode: NSToolbarSizeModeSmall];
1110   [self setDelegate: self];
1111   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1112   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1113   prevEnablement = enablement = 0L;
1114   return self;
1117 - (void)dealloc
1119   [prevIdentifiers release];
1120   [activeIdentifiers release];
1121   [identifierToItem release];
1122   [super dealloc];
1125 - (void) clearActive
1127   [prevIdentifiers release];
1128   prevIdentifiers = [activeIdentifiers copy];
1129   [activeIdentifiers removeAllObjects];
1130   prevEnablement = enablement;
1131   enablement = 0L;
1134 - (BOOL) changed
1136   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1137     enablement == prevEnablement ? NO : YES;
1140 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1141                         helpText: (const char *)help enabled: (BOOL)enabled
1143   /* 1) come up w/identifier */
1144   NSString *identifier
1145       = [NSString stringWithFormat: @"%u", [img hash]];
1147   /* 2) create / reuse item */
1148   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1149   if (item == nil)
1150     {
1151       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1152                autorelease];
1153       [item setImage: img];
1154       [item setToolTip: [NSString stringWithUTF8String: help]];
1155       [item setTarget: emacsView];
1156       [item setAction: @selector (toolbarClicked:)];
1157     }
1159   [item setTag: idx];
1160   [item setEnabled: enabled];
1162   /* 3) update state */
1163   [identifierToItem setObject: item forKey: identifier];
1164   [activeIdentifiers addObject: identifier];
1165   enablement = (enablement << 1) | (enabled == YES);
1168 /* This overrides super's implementation, which automatically sets
1169    all items to enabled state (for some reason). */
1170 - (void)validateVisibleItems { }
1173 /* delegate methods */
1175 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1176       itemForItemIdentifier: (NSString *)itemIdentifier
1177   willBeInsertedIntoToolbar: (BOOL)flag
1179   /* look up NSToolbarItem by identifier and return... */
1180   return [identifierToItem objectForKey: itemIdentifier];
1183 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1185   /* return entire set.. */
1186   return activeIdentifiers;
1189 /* for configuration palette (not yet supported) */
1190 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1192   /* return entire set... */
1193   return [identifierToItem allKeys];
1196 /* optional and unneeded */
1197 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1198 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1199 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1201 @end  /* EmacsToolbar */
1205 /* ==========================================================================
1207     Tooltip: class implementation
1209    ========================================================================== */
1211 /* Needed because NeXTstep does not provide enough control over tooltip
1212    display. */
1213 @implementation EmacsTooltip
1215 - init
1217   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1218                                             blue: 0.792 alpha: 0.95];
1219   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1220   NSFont *sfont = [font screenFont];
1221   int height = [sfont ascender] - [sfont descender];
1222 /*[font boundingRectForFont].size.height; */
1223   NSRect r = NSMakeRect (0, 0, 100, height+6);
1225   textField = [[NSTextField alloc] initWithFrame: r];
1226   [textField setFont: font];
1227   [textField setBackgroundColor: col];
1229   [textField setEditable: NO];
1230   [textField setSelectable: NO];
1231   [textField setBordered: YES];
1232   [textField setBezeled: YES];
1233   [textField setDrawsBackground: YES];
1235   win = [[NSWindow alloc]
1236             initWithContentRect: [textField frame]
1237                       styleMask: 0
1238                         backing: NSBackingStoreBuffered
1239                           defer: YES];
1240   [win setReleasedWhenClosed: NO];
1241   [win setDelegate: self];
1242   [[win contentView] addSubview: textField];
1243 /*  [win setBackgroundColor: col]; */
1244   [win setOpaque: NO];
1246   return self;
1249 - (void) dealloc
1251   [win close];
1252   [win release];
1253   [textField release];
1254   [super dealloc];
1257 - (void) setText: (char *)text
1259   NSString *str = [NSString stringWithUTF8String: text];
1260   NSRect r = [textField frame];
1261   NSSize textSize = [str sizeWithAttributes: 
1262      [NSDictionary dictionaryWithObject: [[textField font] screenFont]
1263                                  forKey: NSFontAttributeName]];
1264   NSSize padSize = [[[textField font] screenFont] 
1265                      boundingRectForFont].size;
1267   r.size.width = textSize.width + padSize.width/2;
1268   r.size.height = textSize.height + padSize.height/2;
1269   [textField setFrame: r];
1270   [textField setStringValue: str];
1273 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1275   NSRect wr = [win frame];
1277   wr.origin = NSMakePoint (x, y);
1278   wr.size = [textField frame].size;
1280   [win setFrame: wr display: YES];
1281   [win orderFront: self];
1282   [win display];
1283   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1284                                          selector: @selector (hide)
1285                                          userInfo: nil repeats: NO];
1286   [timer retain];
1289 - (void) hide
1291   [win close];
1292   if (timer != nil)
1293     {
1294       if ([timer isValid])
1295         [timer invalidate];
1296       [timer release];
1297       timer = nil;
1298     }
1301 - (BOOL) isActive
1303   return timer != nil;
1306 - (NSRect) frame
1308   return [textField frame];
1311 @end  /* EmacsTooltip */
1315 /* ==========================================================================
1317     Popup Dialog: implementing functions
1319    ========================================================================== */
1322 static Lisp_Object
1323 pop_down_menu (Lisp_Object arg)
1325   struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1326   if (popup_activated_flag)
1327     {
1328       popup_activated_flag = 0;
1329       BLOCK_INPUT;
1330       [NSApp endModalSession: popupSession];
1331       [((EmacsDialogPanel *) (p->pointer)) close];
1332       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1333       UNBLOCK_INPUT;
1334     }
1335   return Qnil;
1339 Lisp_Object
1340 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1342   id dialog;
1343   Lisp_Object window, tem;
1344   struct frame *f;
1345   NSPoint p;
1346   BOOL isQ;
1348   NSTRACE (x-popup-dialog);
1349   
1350   check_ns ();
1352   isQ = NILP (header);
1354   if (EQ (position, Qt)
1355       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1356                                || EQ (XCAR (position), Qtool_bar))))
1357     {
1358       window = selected_window;
1359     }
1360   else if (CONSP (position))
1361     {
1362       Lisp_Object tem;
1363       tem = Fcar (position);
1364       if (XTYPE (tem) == Lisp_Cons)
1365         window = Fcar (Fcdr (position));
1366       else
1367         {
1368           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1369           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1370         }
1371     }
1372   else if (WINDOWP (position) || FRAMEP (position))
1373     {
1374       window = position;
1375     }
1376   else
1377     window = Qnil;
1379   if (FRAMEP (window))
1380     f = XFRAME (window);
1381   else if (WINDOWP (window))
1382     {
1383       CHECK_LIVE_WINDOW (window);
1384       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1385     }
1386   else
1387     CHECK_WINDOW (window);
1389   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1390   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1392   BLOCK_INPUT;
1393   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1394                                            isQuestion: isQ];
1395   {
1396     int specpdl_count = SPECPDL_INDEX ();
1397     record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1398     popup_activated_flag = 1;
1399     tem = [dialog runDialogAt: p];
1400     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1401   }
1402   UNBLOCK_INPUT;
1404   return tem;
1408 /* ==========================================================================
1410     Popup Dialog: class implementation
1412    ========================================================================== */
1414 @interface FlippedView : NSView
1417 @end
1419 @implementation FlippedView
1420 - (BOOL)isFlipped
1422   return YES;
1424 @end
1426 @implementation EmacsDialogPanel
1428 #define SPACER          8.0
1429 #define ICONSIZE        64.0
1430 #define TEXTHEIGHT      20.0
1431 #define MINCELLWIDTH    90.0
1433 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1434               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1436   NSSize spacing = {SPACER, SPACER};
1437   NSRect area;
1438   char this_cmd_name[80];
1439   id cell;
1440   static NSImageView *imgView;
1441   static FlippedView *contentView;
1443   if (imgView == nil)
1444     {
1445       NSImage *img;
1446       area.origin.x   = 3*SPACER;
1447       area.origin.y   = 2*SPACER;
1448       area.size.width = ICONSIZE;
1449       area.size.height= ICONSIZE;
1450       img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1451       [img setScalesWhenResized: YES];
1452       [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1453       imgView = [[NSImageView alloc] initWithFrame: area];
1454       [imgView setImage: img];
1455       [imgView setEditable: NO];
1456       [img release];
1457     }
1459   aStyle = NSTitledWindowMask;
1460   flag = YES;
1461   rows = 0;
1462   cols = 1;
1463   [super initWithContentRect: contentRect styleMask: aStyle
1464                      backing: backingType defer: flag];
1465   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1466   [self setContentView: contentView];
1468   [[self contentView] setAutoresizesSubviews: YES];
1470   [[self contentView] addSubview: imgView];
1471   [self setTitle: @""];
1473   area.origin.x   += ICONSIZE+2*SPACER;
1474 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1475   area.size.width = 400;
1476   area.size.height= TEXTHEIGHT;
1477   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1478   [[self contentView] addSubview: command];
1479   [command setStringValue: ns_app_name];
1480   [command setDrawsBackground: NO];
1481   [command setBezeled: NO];
1482   [command setSelectable: NO];
1483   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1485 /*  area.origin.x   = ICONSIZE+2*SPACER;
1486   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1487   area.size.width = 400;
1488   area.size.height= 2;
1489   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1490   [[self contentView] addSubview: tem];
1491   [tem setTitlePosition: NSNoTitle];
1492   [tem setAutoresizingMask: NSViewWidthSizable];*/
1494 /*  area.origin.x = ICONSIZE+2*SPACER; */
1495   area.origin.y += TEXTHEIGHT+SPACER;
1496   area.size.width = 400;
1497   area.size.height= TEXTHEIGHT;
1498   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1499   [[self contentView] addSubview: title];
1500   [title setDrawsBackground: NO];
1501   [title setBezeled: NO];
1502   [title setSelectable: NO];
1503   [title setFont: [NSFont systemFontOfSize: 11.0]];
1505   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1506   [cell setBordered: NO];
1507   [cell setEnabled: NO];
1508   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1509   [cell setBezelStyle: NSRoundedBezelStyle];
1511   matrix = [[NSMatrix alloc] initWithFrame: contentRect 
1512                                       mode: NSHighlightModeMatrix 
1513                                  prototype: cell 
1514                               numberOfRows: 0 
1515                            numberOfColumns: 1];
1516   [[self contentView] addSubview: matrix];
1517   [matrix release];
1518   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1519                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1520   [matrix setIntercellSpacing: spacing];
1522   [self setOneShot: YES];
1523   [self setReleasedWhenClosed: YES];
1524   [self setHidesOnDeactivate: YES];
1525   return self;
1529 - (BOOL)windowShouldClose: (id)sender
1531   [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1532   return NO;
1536 void process_dialog (id window, Lisp_Object list)
1538   Lisp_Object item;
1539   int row = 0;
1541   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1542     {
1543       item = XCAR (list);
1544       if (XTYPE (item) == Lisp_String)
1545         {
1546           [window addString: SDATA (item) row: row++];
1547         }
1548       else if (XTYPE (item) == Lisp_Cons)
1549         {
1550           [window addButton: SDATA (XCAR (item))
1551                       value: XCDR (item) row: row++];
1552         }
1553       else if (NILP (item))
1554         {
1555           [window addSplit];
1556           row = 0;
1557         }
1558     }
1562 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1564   id cell;
1565        
1566   if (row >= rows)
1567     {
1568       [matrix addRow];
1569       rows++;
1570     }
1571   cell = [matrix cellAtRow: row column: cols-1];
1572   [cell setTarget: self];
1573   [cell setAction: @selector (clicked: )];
1574   [cell setTitle: [NSString stringWithUTF8String: str]];
1575   [cell setTag: XHASH (val)];   // FIXME: BIG UGLY HACK!!
1576   [cell setBordered: YES];
1577   [cell setEnabled: YES];
1579   return self;
1583 - addString: (char *)str row: (int)row
1585   id cell;
1586        
1587   if (row >= rows)
1588     {
1589       [matrix addRow];
1590       rows++;
1591     }
1592   cell = [matrix cellAtRow: row column: cols-1];
1593   [cell setTitle: [NSString stringWithUTF8String: str]];
1594   [cell setBordered: YES];
1595   [cell setEnabled: NO];
1597   return self;
1601 - addSplit
1603   [matrix addColumn];
1604   cols++;
1605   return self;
1609 - clicked: sender
1611   NSArray *sellist = nil;
1612   EMACS_INT seltag;
1614   sellist = [sender selectedCells];
1615   if ([sellist count]<1) 
1616     return self;
1618   seltag = [[sellist objectAtIndex: 0] tag];
1619   if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1620     [NSApp stopModalWithCode: seltag];
1621   return self;
1625 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1627   Lisp_Object head;
1628   [super init];
1630   if (XTYPE (contents) == Lisp_Cons)
1631     {
1632       head = Fcar (contents);
1633       process_dialog (self, Fcdr (contents));
1634     }
1635   else
1636     head = contents;
1638   if (XTYPE (head) == Lisp_String)
1639       [title setStringValue:
1640                  [NSString stringWithUTF8String: SDATA (head)]];
1641   else if (isQ == YES)
1642       [title setStringValue: @"Question"];
1643   else
1644       [title setStringValue: @"Information"];
1646   {
1647     int i;
1648     NSRect r, s, t;
1650     if (cols == 1 && rows > 1)  /* Never told where to split */
1651       {
1652         [matrix addColumn];
1653         for (i = 0; i<rows/2; i++)
1654           {
1655             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1656                       atRow: i column: 1];
1657             [matrix removeRow: (rows+1)/2];
1658           }
1659       }
1661     [matrix sizeToFit];
1662     {
1663       NSSize csize = [matrix cellSize];
1664       if (csize.width < MINCELLWIDTH)
1665         {
1666           csize.width = MINCELLWIDTH;
1667           [matrix setCellSize: csize];
1668           [matrix sizeToCells];
1669         }
1670     }
1672     [title sizeToFit];
1673     [command sizeToFit];
1675     t = [matrix frame];
1676     r = [title frame];
1677     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1678       {
1679         t.origin.x   = r.origin.x;
1680         t.size.width = r.size.width;
1681       }
1682     r = [command frame];
1683     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1684       {
1685         t.origin.x   = r.origin.x;
1686         t.size.width = r.size.width;
1687       }
1689     r = [self frame];
1690     s = [(NSView *)[self contentView] frame];
1691     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1692     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1693     [self setFrame: r display: NO];
1694   }
1696   return self;
1700 - (void)dealloc
1702   { [super dealloc]; return; };
1706 - (Lisp_Object)runDialogAt: (NSPoint)p
1708   NSInteger ret;
1709   extern EMACS_TIME timer_check (int do_it_now); /* TODO: add to a header */
1711   /* initiate a session that will be ended by pop_down_menu */
1712   popupSession = [NSApp beginModalSessionForWindow: self];
1713   while (popup_activated_flag
1714          && (ret = [NSApp runModalSession: popupSession])
1715               == NSRunContinuesResponse)
1716     {
1717       /* Run this for timers.el, indep of atimers; might not return.
1718          TODO: use return value to avoid calling every iteration. */
1719       timer_check (1);
1720       [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1721     }
1723   {                             /* FIXME: BIG UGLY HACK!!! */
1724       Lisp_Object tmp;
1725       *(EMACS_INT*)(&tmp) = ret;
1726       return tmp;
1727   }
1730 @end
1733 /* ==========================================================================
1735     Lisp definitions
1737    ========================================================================== */
1739 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1740        doc: /* Cause the NS menu to be re-calculated.  */)
1741      (void)
1743   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1744   return Qnil;
1748 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1749        doc: /* Pop up a dialog box and return user's selection.
1750 POSITION specifies which frame to use.
1751 This is normally a mouse button event or a window or frame.
1752 If POSITION is t, it means to use the frame the mouse is on.
1753 The dialog box appears in the middle of the specified frame.
1755 CONTENTS specifies the alternatives to display in the dialog box.
1756 It is a list of the form (DIALOG ITEM1 ITEM2...).
1757 Each ITEM is a cons cell (STRING . VALUE).
1758 The return value is VALUE from the chosen item.
1760 An ITEM may also be just a string--that makes a nonselectable item.
1761 An ITEM may also be nil--that means to put all preceding items
1762 on the left of the dialog box and all following items on the right.
1763 \(By default, approximately half appear on each side.)
1765 If HEADER is non-nil, the frame title for the box is "Information",
1766 otherwise it is "Question".
1768 If the user gets rid of the dialog box without making a valid choice,
1769 for instance using the window manager, then this produces a quit and
1770 `x-popup-dialog' does not return.  */)
1771      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1773   return ns_popup_dialog (position, contents, header);
1776 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1777        doc: /* Return t if a menu or popup dialog is active.  */)
1778      (void)
1780   return popup_activated () ? Qt : Qnil;
1783 /* ==========================================================================
1785     Lisp interface declaration
1787    ========================================================================== */
1789 void
1790 syms_of_nsmenu (void)
1792   defsubr (&Sx_popup_dialog);
1793   defsubr (&Sns_reset_menu);
1794   defsubr (&Smenu_or_popup_active_p);
1796   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1797   staticpro (&Qdebug_on_next_call);
1800 // arch-tag: 75773656-52e5-4c44-a398-47bd87b32619