Merge from emacs-24; up to 2012-12-23T02:41:17Z!rgm@gnu.org
[emacs.git] / src / nsmenu.m
blob22ff4dd0b53bf40f68d1bb82780f117c8d49573d
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007-2013 Free Software Foundation, Inc.
4 This file is part of GNU Emacs.
6 GNU Emacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 GNU Emacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
20 By Adrian Robert, based on code from original nsmenu.m (Carl Edman,
21 Christian Limpach, Scott Bender, Christophe de Dinechin) and code in the
22 Carbon version by Yamamoto Mitsuharu. */
24 /* This should be the first include, as it may set up #defines affecting
25    interpretation of even the system includes. */
26 #include <config.h>
28 #include "lisp.h"
29 #include "window.h"
30 #include "character.h"
31 #include "buffer.h"
32 #include "keymap.h"
33 #include "coding.h"
34 #include "commands.h"
35 #include "blockinput.h"
36 #include "nsterm.h"
37 #include "termhooks.h"
38 #include "keyboard.h"
39 #include "menu.h"
41 #define NSMENUPROFILE 0
43 #if NSMENUPROFILE
44 #include <sys/timeb.h>
45 #include <sys/types.h>
46 #endif
48 #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;
76 /* Nonzero means we are tracking and updating menus.  */
77 static int trackingMenu;
80 /* NOTE: toolbar implementation is at end,
81   following complete menu implementation. */
84 /* ==========================================================================
86     Menu: Externally-called functions
88    ========================================================================== */
91 /* FIXME: not currently used, but should normalize with other terms. */
92 void
93 x_activate_menubar (struct frame *f)
95     fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
99 /* Supposed to discard menubar and free storage.  Since we share the
100    menubar among frames and update its context for the focused window,
101    there is nothing to do here. */
102 void
103 free_frame_menubar (struct frame *f)
105   return;
110 popup_activated (void)
112   return popup_activated_flag;
116 /* --------------------------------------------------------------------------
117     Update menubar.  Three cases:
118     1) ! deep_p, submenu = nil: Fresh switch onto a frame -- either set up
119        just top-level menu strings (OS X), or goto case (2) (GNUstep).
120     2) deep_p, submenu = nil: Recompute all submenus.
121     3) deep_p, submenu = non-nil: Update contents of a single submenu.
122    -------------------------------------------------------------------------- */
123 void
124 ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu)
126   NSAutoreleasePool *pool;
127   id menu = [NSApp mainMenu];
128   static EmacsMenu *last_submenu = nil;
129   BOOL needsSet = NO;
130   const char *submenuTitle = [[submenu title] UTF8String];
131   bool owfi;
132   Lisp_Object items;
133   widget_value *wv, *first_wv, *prev_wv = 0;
134   int i;
136 #if NSMENUPROFILE
137   struct timeb tb;
138   long t;
139 #endif
141   NSTRACE (set_frame_menubar);
143   if (f != SELECTED_FRAME ())
144       return;
145   XSETFRAME (Vmenu_updating_frame, f);
146 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
148   block_input ();
149   pool = [[NSAutoreleasePool alloc] init];
151   /* Menu may have been created automatically; if so, discard it. */
152   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
153     {
154       [menu release];
155       menu = nil;
156     }
158   if (menu == nil)
159     {
160       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
161       needsSet = YES;
162     }
163   else
164     {  /* close up anything on there */
165       id attMenu = [menu attachedMenu];
166       if (attMenu != nil)
167         [attMenu close];
168     }
170 #if NSMENUPROFILE
171   ftime (&tb);
172   t = -(1000*tb.time+tb.millitm);
173 #endif
175 #ifdef NS_IMPL_GNUSTEP
176   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
177 #endif
179   if (deep_p)
180     {
181       /* Fully parse one or more of the submenus. */
182       int n = 0;
183       int *submenu_start, *submenu_end;
184       bool *submenu_top_level_items;
185       int *submenu_n_panes;
186       struct buffer *prev = current_buffer;
187       Lisp_Object buffer;
188       ptrdiff_t specpdl_count = SPECPDL_INDEX ();
189       int previous_menu_items_used = f->menu_bar_items_used;
190       Lisp_Object *previous_items
191         = alloca (previous_menu_items_used * sizeof *previous_items);
193       /* lisp preliminaries */
194       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
195       specbind (Qinhibit_quit, Qt);
196       specbind (Qdebug_on_next_call, Qnil);
197       record_unwind_save_match_data ();
198       if (NILP (Voverriding_local_map_menu_flag))
199         {
200           specbind (Qoverriding_terminal_local_map, Qnil);
201           specbind (Qoverriding_local_map, Qnil);
202         }
203       set_buffer_internal_1 (XBUFFER (buffer));
205       /* TODO: for some reason this is not needed in other terms,
206            but some menu updates call Info-extract-pointer which causes
207            abort-on-error if waiting-for-input.  Needs further investigation. */
208       owfi = waiting_for_input;
209       waiting_for_input = 0;
211       /* lucid hook and possible reset */
212       safe_run_hooks (Qactivate_menubar_hook);
213       if (! NILP (Vlucid_menu_bar_dirty_flag))
214         call0 (Qrecompute_lucid_menubar);
215       safe_run_hooks (Qmenu_bar_update_hook);
216       fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
218       /* Now ready to go */
219       items = FRAME_MENU_BAR_ITEMS (f);
221       /* Save the frame's previous menu bar contents data */
222       if (previous_menu_items_used)
223         memcpy (previous_items, aref_addr (f->menu_bar_vector, 0),
224                 previous_menu_items_used * sizeof (Lisp_Object));
226       /* parse stage 1: extract from lisp */
227       save_menu_items ();
229       menu_items = f->menu_bar_vector;
230       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
231       submenu_start = alloca (ASIZE (items) * sizeof *submenu_start);
232       submenu_end = alloca (ASIZE (items) * sizeof *submenu_end);
233       submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes);
234       submenu_top_level_items = alloca (ASIZE (items)
235                                         * sizeof *submenu_top_level_items);
236       init_menu_items ();
237       for (i = 0; i < ASIZE (items); i += 4)
238         {
239           Lisp_Object key, string, maps;
241           key = AREF (items, i);
242           string = AREF (items, i + 1);
243           maps = AREF (items, i + 2);
244           if (NILP (string))
245             break;
247           /* FIXME: we'd like to only parse the needed submenu, but this
248                was causing crashes in the _common parsing code.. need to make
249                sure proper initialization done.. */
250 /*        if (submenu && strcmp (submenuTitle, SSDATA (string)))
251              continue; */
253           submenu_start[i] = menu_items_used;
255           menu_items_n_panes = 0;
256           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
257           submenu_n_panes[i] = menu_items_n_panes;
258           submenu_end[i] = menu_items_used;
259           n++;
260         }
262       finish_menu_items ();
263       waiting_for_input = owfi;
266       if (submenu && n == 0)
267         {
268           /* should have found a menu for this one but didn't */
269           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
270                   submenuTitle);
271           discard_menu_items ();
272           unbind_to (specpdl_count, Qnil);
273           [pool release];
274           unblock_input ();
275           return;
276         }
278       /* parse stage 2: insert into lucid 'widget_value' structures
279          [comments in other terms say not to evaluate lisp code here] */
280       wv = xmalloc_widget_value ();
281       wv->name = "menubar";
282       wv->value = 0;
283       wv->enabled = 1;
284       wv->button_type = BUTTON_TYPE_NONE;
285       wv->help = Qnil;
286       first_wv = wv;
288       for (i = 0; i < 4*n; i += 4)
289         {
290           menu_items_n_panes = submenu_n_panes[i];
291           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
292                                       submenu_top_level_items[i]);
293           if (prev_wv)
294             prev_wv->next = wv;
295           else
296             first_wv->contents = wv;
297           /* Don't set wv->name here; GC during the loop might relocate it.  */
298           wv->enabled = 1;
299           wv->button_type = BUTTON_TYPE_NONE;
300           prev_wv = wv;
301         }
303       set_buffer_internal_1 (prev);
305       /* Compare the new menu items with previous, and leave off if no change */
306       /* FIXME: following other terms here, but seems like this should be
307            done before parse stage 2 above, since its results aren't used */
308       if (previous_menu_items_used
309           && (!submenu || (submenu && submenu == last_submenu))
310           && menu_items_used == previous_menu_items_used)
311         {
312           for (i = 0; i < previous_menu_items_used; i++)
313             /* FIXME: this ALWAYS fails on Buffers menu items.. something
314                  about their strings causes them to change every time, so we
315                  double-check failures */
316             if (!EQ (previous_items[i], AREF (menu_items, i)))
317               if (!(STRINGP (previous_items[i])
318                     && STRINGP (AREF (menu_items, i))
319                     && !strcmp (SSDATA (previous_items[i]),
320                                 SSDATA (AREF (menu_items, i)))))
321                   break;
322           if (i == previous_menu_items_used)
323             {
324               /* No change.. */
326 #if NSMENUPROFILE
327               ftime (&tb);
328               t += 1000*tb.time+tb.millitm;
329               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
330 #endif
332               free_menubar_widget_value_tree (first_wv);
333               discard_menu_items ();
334               unbind_to (specpdl_count, Qnil);
335               [pool release];
336               unblock_input ();
337               return;
338             }
339         }
340       /* The menu items are different, so store them in the frame */
341       /* FIXME: this is not correct for single-submenu case */
342       fset_menu_bar_vector (f, menu_items);
343       f->menu_bar_items_used = menu_items_used;
345       /* Calls restore_menu_items, etc., as they were outside */
346       unbind_to (specpdl_count, Qnil);
348       /* Parse stage 2a: now GC cannot happen during the lifetime of the
349          widget_value, so it's safe to store data from a Lisp_String */
350       wv = first_wv->contents;
351       for (i = 0; i < ASIZE (items); i += 4)
352         {
353           Lisp_Object string;
354           string = AREF (items, i + 1);
355           if (NILP (string))
356             break;
357 /*           if (submenu && strcmp (submenuTitle, SSDATA (string)))
358                continue; */
360           wv->name = SSDATA (string);
361           update_submenu_strings (wv->contents);
362           wv = wv->next;
363         }
365       /* Now, update the NS menu; if we have a submenu, use that, otherwise
366          create a new menu for each sub and fill it. */
367       if (submenu)
368         {
369           for (wv = first_wv->contents; wv; wv = wv->next)
370             {
371               if (!strcmp (submenuTitle, wv->name))
372                 {
373                   [submenu fillWithWidgetValue: wv->contents];
374                   last_submenu = submenu;
375                   break;
376                 }
377             }
378         }
379       else
380         {
381           [menu fillWithWidgetValue: first_wv->contents];
382         }
384     }
385   else
386     {
387       static int n_previous_strings = 0;
388       static char previous_strings[100][10];
389       static struct frame *last_f = NULL;
390       int n;
391       Lisp_Object string;
393       wv = xmalloc_widget_value ();
394       wv->name = "menubar";
395       wv->value = 0;
396       wv->enabled = 1;
397       wv->button_type = BUTTON_TYPE_NONE;
398       wv->help = Qnil;
399       first_wv = wv;
401       /* Make widget-value tree w/ just the top level menu bar strings */
402       items = FRAME_MENU_BAR_ITEMS (f);
403       if (NILP (items))
404         {
405           free_menubar_widget_value_tree (first_wv);
406           [pool release];
407           unblock_input ();
408           return;
409         }
412       /* check if no change.. this mechanism is a bit rough, but ready */
413       n = ASIZE (items) / 4;
414       if (f == last_f && n_previous_strings == n)
415         {
416           for (i = 0; i<n; i++)
417             {
418               string = AREF (items, 4*i+1);
420               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
421                 continue;
422               if (NILP (string))
423                 {
424                   if (previous_strings[i][0])
425                     break;
426                   else
427                     continue;
428                 }
429               else if (memcmp (previous_strings[i], SDATA (string),
430                           min (10, SBYTES (string) + 1)))
431                 break;
432             }
434           if (i == n)
435             {
436               free_menubar_widget_value_tree (first_wv);
437               [pool release];
438               unblock_input ();
439               return;
440             }
441         }
443       [menu clear];
444       for (i = 0; i < ASIZE (items); i += 4)
445         {
446           string = AREF (items, i + 1);
447           if (NILP (string))
448             break;
450           if (n < 100)
451             memcpy (previous_strings[i/4], SDATA (string),
452                     min (10, SBYTES (string) + 1));
454           wv = xmalloc_widget_value ();
455           wv->name = SSDATA (string);
456           wv->value = 0;
457           wv->enabled = 1;
458           wv->button_type = BUTTON_TYPE_NONE;
459           wv->help = Qnil;
460           wv->call_data = (void *) (intptr_t) (-1);
462 #ifdef NS_IMPL_COCOA
463           /* we'll update the real copy under app menu when time comes */
464           if (!strcmp ("Services", wv->name))
465             {
466               /* but we need to make sure it will update on demand */
467               [svcsMenu setFrame: f];
468             }
469           else
470 #endif
471           [menu addSubmenuWithTitle: wv->name forFrame: f];
473           if (prev_wv)
474             prev_wv->next = wv;
475           else
476             first_wv->contents = wv;
477           prev_wv = wv;
478         }
480       last_f = f;
481       if (n < 100)
482         n_previous_strings = n;
483       else
484         n_previous_strings = 0;
486     }
487   free_menubar_widget_value_tree (first_wv);
490 #if NSMENUPROFILE
491   ftime (&tb);
492   t += 1000*tb.time+tb.millitm;
493   fprintf (stderr, "Menu update took %ld msec.\n", t);
494 #endif
496   /* set main menu */
497   if (needsSet)
498     [NSApp setMainMenu: menu];
500   [pool release];
501   unblock_input ();
506 /* Main emacs core entry point for menubar menus: called to indicate that the
507    frame's menus have changed, and the *step representation should be updated
508    from Lisp. */
509 void
510 set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
512   ns_update_menubar (f, deep_p, nil);
516 /* ==========================================================================
518     Menu: class implementation
520    ========================================================================== */
523 /* Menu that can define itself from Emacs "widget_value"s and will lazily
524    update itself when user clicked.  Based on Carbon/AppKit implementation
525    by Yamamoto Mitsuharu. */
526 @implementation EmacsMenu
528 /* override designated initializer */
529 - initWithTitle: (NSString *)title
531   if ((self = [super initWithTitle: title]))
532     [self setAutoenablesItems: NO];
533   return self;
537 /* used for top-level */
538 - initWithTitle: (NSString *)title frame: (struct frame *)f
540   [self initWithTitle: title];
541   frame = f;
542 #ifdef NS_IMPL_COCOA
543   [self setDelegate: self];
544 #endif
545   return self;
549 - (void)setFrame: (struct frame *)f
551   frame = f;
554 #ifdef NS_IMPL_COCOA
555 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
556 extern NSString *NSMenuDidBeginTrackingNotification;
557 #endif
558 #endif
560 #ifdef NS_IMPL_COCOA
561 -(void)trackingNotification:(NSNotification *)notification
563   /* Update menu in menuNeedsUpdate only while tracking menus.  */
564   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
565                   ? 1 : 0);
567 #endif
569 /* delegate method called when a submenu is being opened: run a 'deep' call
570    to set_frame_menubar */
571 - (void)menuNeedsUpdate: (NSMenu *)menu
573   if (!FRAME_LIVE_P (frame))
574     return;
576   /* Cocoa/Carbon will request update on every keystroke
577      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
578      since key equivalents are handled through emacs.
579      On Leopard, even keystroke events generate SystemDefined event.
580      Third-party applications that enhance mouse / trackpad
581      interaction, or also VNC/Remote Desktop will send events
582      of type AppDefined rather than SysDefined.
583      Menus will fail to show up if they haven't been initialized.
584      AppDefined events may lack timing data.
586      Thus, we rely on the didBeginTrackingNotification notification
587      as above to indicate the need for updates.
588      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
589      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
590   */
591   if (trackingMenu == 0)
592     return;
593 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
594   ns_update_menubar (frame, 1, self);
598 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
600   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
601       && FRAME_NS_VIEW (SELECTED_FRAME ()))
602     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
603   return YES;
607 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
608    into an accelerator string.  We are only able to display a single character
609    for an accelerator, together with an optional modifier combination.  (Under
610    Carbon more control was possible, but in Cocoa multi-char strings passed to
611    NSMenuItem get ignored.  For now we try to display a super-single letter
612    combo, and return the others as strings to be appended to the item title.
613    (This is signaled by setting keyEquivModMask to 0 for now.) */
614 -(NSString *)parseKeyEquiv: (const char *)key
616   const char *tpos = key;
617   keyEquivModMask = NSCommandKeyMask;
619   if (!key || !strlen (key))
620     return @"";
622   while (*tpos == ' ' || *tpos == '(')
623     tpos++;
624   if ((*tpos == 's') && (*(tpos+1) == '-'))
625     {
626       return [NSString stringWithFormat: @"%c", tpos[2]];
627     }
628   keyEquivModMask = 0; /* signal */
629   return [NSString stringWithUTF8String: tpos];
633 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
635   NSMenuItem *item;
636   widget_value *wv = (widget_value *)wvptr;
638   if (menu_separator_name_p (wv->name))
639     {
640       item = [NSMenuItem separatorItem];
641       [self addItem: item];
642     }
643   else
644     {
645       NSString *title, *keyEq;
646       title = [NSString stringWithUTF8String: wv->name];
647       if (title == nil)
648         title = @"< ? >";  /* (get out in the open so we know about it) */
650       keyEq = [self parseKeyEquiv: wv->key];
651 #ifdef NS_IMPL_COCOA
652       /* OS X just ignores modifier strings longer than one character */
653       if (keyEquivModMask == 0)
654         title = [title stringByAppendingFormat: @" (%@)", keyEq];
655 #endif
657       item = [self addItemWithTitle: (NSString *)title
658                              action: @selector (menuDown:)
659                       keyEquivalent: keyEq];
660       [item setKeyEquivalentModifierMask: keyEquivModMask];
662       [item setEnabled: wv->enabled];
664       /* Draw radio buttons and tickboxes */
665       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
666                            wv->button_type == BUTTON_TYPE_RADIO))
667         [item setState: NSOnState];
668       else
669         [item setState: NSOffState];
671       [item setTag: (NSInteger)wv->call_data];
672     }
674   return item;
678 /* convenience */
679 -(void)clear
681   int n;
683   for (n = [self numberOfItems]-1; n >= 0; n--)
684     {
685       NSMenuItem *item = [self itemAtIndex: n];
686       NSString *title = [item title];
687       if (([title length] == 0  /* OSX 10.5 */
688            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
689            || [@"Apple" isEqualToString: title]) /* older */
690           && ![item isSeparatorItem])
691         continue;
692       [self removeItemAtIndex: n];
693     }
697 - (void)fillWithWidgetValue: (void *)wvptr
699   widget_value *wv = (widget_value *)wvptr;
701   /* clear existing contents */
702   [self setMenuChangedMessagesEnabled: NO];
703   [self clear];
705   /* add new contents */
706   for (; wv != NULL; wv = wv->next)
707     {
708       NSMenuItem *item = [self addItemWithWidgetValue: wv];
710       if (wv->contents)
711         {
712           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
714           [self setSubmenu: submenu forItem: item];
715           [submenu fillWithWidgetValue: wv->contents];
716           [submenu release];
717           [item setAction: nil];
718         }
719     }
721   [self setMenuChangedMessagesEnabled: YES];
722 #ifdef NS_IMPL_GNUSTEP
723   if ([[self window] isVisible])
724     [self sizeToFit];
725 #endif
729 /* adds an empty submenu and returns it */
730 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
732   NSString *titleStr = [NSString stringWithUTF8String: title];
733   NSMenuItem *item = [self addItemWithTitle: titleStr
734                                      action: nil /*@selector (menuDown:) */
735                               keyEquivalent: @""];
736   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
737   [self setSubmenu: submenu forItem: item];
738   [submenu release];
739   return submenu;
742 /* run a menu in popup mode */
743 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
744                  keymaps: (bool)keymaps
746   EmacsView *view = FRAME_NS_VIEW (f);
747   NSEvent *e, *event;
748   long retVal;
750 /*   p = [view convertPoint:p fromView: nil]; */
751   p.y = NSHeight ([view frame]) - p.y;
752   e = [[view window] currentEvent];
753    event = [NSEvent mouseEventWithType: NSRightMouseDown
754                               location: p
755                          modifierFlags: 0
756                              timestamp: [e timestamp]
757                           windowNumber: [[view window] windowNumber]
758                                context: [e context]
759                            eventNumber: 0/*[e eventNumber] */
760                             clickCount: 1
761                               pressure: 0];
763   context_menu_value = -1;
764   [NSMenu popUpContextMenu: self withEvent: event forView: view];
765   retVal = context_menu_value;
766   context_menu_value = 0;
767   return retVal > 0
768       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
769       : Qnil;
772 @end  /* EmacsMenu */
776 /* ==========================================================================
778     Context Menu: implementing functions
780    ========================================================================== */
782 Lisp_Object
783 ns_menu_show (FRAME_PTR f, int x, int y, bool for_click, bool keymaps,
784               Lisp_Object title, const char **error)
786   EmacsMenu *pmenu;
787   NSPoint p;
788   Lisp_Object tem;
789   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
790   widget_value *wv, *first_wv = 0;
792   p.x = x; p.y = y;
794   /* now parse stage 2 as in ns_update_menubar */
795   wv = xmalloc_widget_value ();
796   wv->name = "contextmenu";
797   wv->value = 0;
798   wv->enabled = 1;
799   wv->button_type = BUTTON_TYPE_NONE;
800   wv->help = Qnil;
801   first_wv = wv;
803 #if 0
804   /* FIXME: a couple of one-line differences prevent reuse */
805   wv = digest_single_submenu (0, menu_items_used, 0);
806 #else
807   {
808   widget_value *save_wv = 0, *prev_wv = 0;
809   widget_value **submenu_stack
810     = alloca (menu_items_used * sizeof *submenu_stack);
811 /*   Lisp_Object *subprefix_stack
812        = alloca (menu_items_used * sizeof *subprefix_stack); */
813   int submenu_depth = 0;
814   int first_pane = 1;
815   int i;
817   /* Loop over all panes and items, filling in the tree.  */
818   i = 0;
819   while (i < menu_items_used)
820     {
821       if (EQ (AREF (menu_items, i), Qnil))
822         {
823           submenu_stack[submenu_depth++] = save_wv;
824           save_wv = prev_wv;
825           prev_wv = 0;
826           first_pane = 1;
827           i++;
828         }
829       else if (EQ (AREF (menu_items, i), Qlambda))
830         {
831           prev_wv = save_wv;
832           save_wv = submenu_stack[--submenu_depth];
833           first_pane = 0;
834           i++;
835         }
836       else if (EQ (AREF (menu_items, i), Qt)
837                && submenu_depth != 0)
838         i += MENU_ITEMS_PANE_LENGTH;
839       /* Ignore a nil in the item list.
840          It's meaningful only for dialog boxes.  */
841       else if (EQ (AREF (menu_items, i), Qquote))
842         i += 1;
843       else if (EQ (AREF (menu_items, i), Qt))
844         {
845           /* Create a new pane.  */
846           Lisp_Object pane_name, prefix;
847           const char *pane_string;
849           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
850           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
852 #ifndef HAVE_MULTILINGUAL_MENU
853           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
854             {
855               pane_name = ENCODE_MENU_STRING (pane_name);
856               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
857             }
858 #endif
859           pane_string = (NILP (pane_name)
860                          ? "" : SSDATA (pane_name));
861           /* If there is just one top-level pane, put all its items directly
862              under the top-level menu.  */
863           if (menu_items_n_panes == 1)
864             pane_string = "";
866           /* If the pane has a meaningful name,
867              make the pane a top-level menu item
868              with its items as a submenu beneath it.  */
869           if (!keymaps && strcmp (pane_string, ""))
870             {
871               wv = xmalloc_widget_value ();
872               if (save_wv)
873                 save_wv->next = wv;
874               else
875                 first_wv->contents = wv;
876               wv->name = pane_string;
877               if (keymaps && !NILP (prefix))
878                 wv->name++;
879               wv->value = 0;
880               wv->enabled = 1;
881               wv->button_type = BUTTON_TYPE_NONE;
882               wv->help = Qnil;
883               save_wv = wv;
884               prev_wv = 0;
885             }
886           else if (first_pane)
887             {
888               save_wv = wv;
889               prev_wv = 0;
890             }
891           first_pane = 0;
892           i += MENU_ITEMS_PANE_LENGTH;
893         }
894       else
895         {
896           /* Create a new item within current pane.  */
897           Lisp_Object item_name, enable, descrip, def, type, selected, help;
898           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
899           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
900           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
901           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
902           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
903           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
904           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
906 #ifndef HAVE_MULTILINGUAL_MENU
907           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
908             {
909               item_name = ENCODE_MENU_STRING (item_name);
910               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
911             }
913           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
914             {
915               descrip = ENCODE_MENU_STRING (descrip);
916               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
917             }
918 #endif /* not HAVE_MULTILINGUAL_MENU */
920           wv = xmalloc_widget_value ();
921           if (prev_wv)
922             prev_wv->next = wv;
923           else
924             save_wv->contents = wv;
925           wv->name = SSDATA (item_name);
926           if (!NILP (descrip))
927             wv->key = SSDATA (descrip);
928           wv->value = 0;
929           /* If this item has a null value,
930              make the call_data null so that it won't display a box
931              when the mouse is on it.  */
932           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
933           wv->enabled = !NILP (enable);
935           if (NILP (type))
936             wv->button_type = BUTTON_TYPE_NONE;
937           else if (EQ (type, QCtoggle))
938             wv->button_type = BUTTON_TYPE_TOGGLE;
939           else if (EQ (type, QCradio))
940             wv->button_type = BUTTON_TYPE_RADIO;
941           else
942             emacs_abort ();
944           wv->selected = !NILP (selected);
946           if (! STRINGP (help))
947             help = Qnil;
949           wv->help = help;
951           prev_wv = wv;
953           i += MENU_ITEMS_ITEM_LENGTH;
954         }
955     }
956   }
957 #endif
959   if (!NILP (title))
960     {
961       widget_value *wv_title = xmalloc_widget_value ();
962       widget_value *wv_sep = xmalloc_widget_value ();
964       /* Maybe replace this separator with a bitmap or owner-draw item
965          so that it looks better.  Having two separators looks odd.  */
966       wv_sep->name = "--";
967       wv_sep->next = first_wv->contents;
968       wv_sep->help = Qnil;
970 #ifndef HAVE_MULTILINGUAL_MENU
971       if (STRING_MULTIBYTE (title))
972         title = ENCODE_MENU_STRING (title);
973 #endif
975       wv_title->name = SSDATA (title);
976       wv_title->enabled = NO;
977       wv_title->button_type = BUTTON_TYPE_NONE;
978       wv_title->help = Qnil;
979       wv_title->next = wv_sep;
980       first_wv->contents = wv_title;
981     }
983   pmenu = [[EmacsMenu alloc] initWithTitle:
984                                [NSString stringWithUTF8String: SSDATA (title)]];
985   [pmenu fillWithWidgetValue: first_wv->contents];
986   free_menubar_widget_value_tree (first_wv);
987   unbind_to (specpdl_count, Qnil);
989   popup_activated_flag = 1;
990   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
991   popup_activated_flag = 0;
992   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
994   return tem;
998 /* ==========================================================================
1000     Toolbar: externally-called functions
1002    ========================================================================== */
1004 void
1005 free_frame_tool_bar (FRAME_PTR f)
1006 /* --------------------------------------------------------------------------
1007     Under NS we just hide the toolbar until it might be needed again.
1008    -------------------------------------------------------------------------- */
1010   block_input ();
1011   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1012   FRAME_TOOLBAR_HEIGHT (f) = 0;
1013   unblock_input ();
1016 void
1017 update_frame_tool_bar (FRAME_PTR f)
1018 /* --------------------------------------------------------------------------
1019     Update toolbar contents
1020    -------------------------------------------------------------------------- */
1022   int i;
1023   EmacsView *view = FRAME_NS_VIEW (f);
1024   NSWindow *window = [view window];
1025   EmacsToolbar *toolbar = [view toolbar];
1027   block_input ();
1028   [toolbar clearActive];
1030   /* update EmacsToolbar as in GtkUtils, build items list */
1031   for (i = 0; i < f->n_tool_bar_items; ++i)
1032     {
1033 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1034                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1036       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1037       int idx;
1038       ptrdiff_t img_id;
1039       struct image *img;
1040       Lisp_Object image;
1041       Lisp_Object helpObj;
1042       const char *helpText;
1044       /* If image is a vector, choose the image according to the
1045          button state.  */
1046       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1047       if (VECTORP (image))
1048         {
1049           /* NS toolbar auto-computes disabled and selected images */
1050           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1051           eassert (ASIZE (image) >= idx);
1052           image = AREF (image, idx);
1053         }
1054       else
1055         {
1056           idx = -1;
1057         }
1058       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1059       if (NILP (helpObj))
1060         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1061       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1063       /* Ignore invalid image specifications.  */
1064       if (!valid_image_p (image))
1065         {
1066           /* Don't log anything, GNUS makes invalid images all the time.  */
1067           continue;
1068         }
1070       img_id = lookup_image (f, image);
1071       img = IMAGE_FROM_ID (f, img_id);
1072       prepare_image_for_display (f, img);
1074       if (img->load_failed_p || img->pixmap == nil)
1075         {
1076           NSLog (@"Could not prepare toolbar image for display.");
1077           continue;
1078         }
1080       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1081                                enabled: enabled_p];
1082 #undef TOOLPROP
1083     }
1085   if (![toolbar isVisible])
1086       [toolbar setVisible: YES];
1088   if ([toolbar changed])
1089     {
1090       /* inform app that toolbar has changed */
1091       NSDictionary *dict = [toolbar configurationDictionary];
1092       NSMutableDictionary *newDict = [dict mutableCopy];
1093       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1094       id key;
1095       while ((key = [keys nextObject]) != nil)
1096         {
1097           NSObject *val = [dict objectForKey: key];
1098           if ([val isKindOfClass: [NSArray class]])
1099             {
1100               [newDict setObject:
1101                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1102                           forKey: key];
1103               break;
1104             }
1105         }
1106       [toolbar setConfigurationFromDictionary: newDict];
1107       [newDict release];
1108     }
1110   FRAME_TOOLBAR_HEIGHT (f) =
1111     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1112     - FRAME_NS_TITLEBAR_HEIGHT (f);
1113     if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1114       FRAME_TOOLBAR_HEIGHT (f) = 0;
1115     unblock_input ();
1119 /* ==========================================================================
1121     Toolbar: class implementation
1123    ========================================================================== */
1125 @implementation EmacsToolbar
1127 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1129   self = [super initWithIdentifier: identifier];
1130   emacsView = view;
1131   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1132   [self setSizeMode: NSToolbarSizeModeSmall];
1133   [self setDelegate: self];
1134   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1135   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1136   prevEnablement = enablement = 0L;
1137   return self;
1140 - (void)dealloc
1142   [prevIdentifiers release];
1143   [activeIdentifiers release];
1144   [identifierToItem release];
1145   [super dealloc];
1148 - (void) clearActive
1150   [prevIdentifiers release];
1151   prevIdentifiers = [activeIdentifiers copy];
1152   [activeIdentifiers removeAllObjects];
1153   prevEnablement = enablement;
1154   enablement = 0L;
1157 - (BOOL) changed
1159   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1160     enablement == prevEnablement ? NO : YES;
1163 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1164                         helpText: (const char *)help enabled: (BOOL)enabled
1166   /* 1) come up w/identifier */
1167   NSString *identifier
1168       = [NSString stringWithFormat: @"%u", [img hash]];
1170   /* 2) create / reuse item */
1171   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1172   if (item == nil)
1173     {
1174       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1175                autorelease];
1176       [item setImage: img];
1177       [item setToolTip: [NSString stringWithUTF8String: help]];
1178       [item setTarget: emacsView];
1179       [item setAction: @selector (toolbarClicked:)];
1180     }
1182   [item setTag: idx];
1183   [item setEnabled: enabled];
1185   /* 3) update state */
1186   [identifierToItem setObject: item forKey: identifier];
1187   [activeIdentifiers addObject: identifier];
1188   enablement = (enablement << 1) | (enabled == YES);
1191 /* This overrides super's implementation, which automatically sets
1192    all items to enabled state (for some reason). */
1193 - (void)validateVisibleItems { }
1196 /* delegate methods */
1198 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1199       itemForItemIdentifier: (NSString *)itemIdentifier
1200   willBeInsertedIntoToolbar: (BOOL)flag
1202   /* look up NSToolbarItem by identifier and return... */
1203   return [identifierToItem objectForKey: itemIdentifier];
1206 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1208   /* return entire set.. */
1209   return activeIdentifiers;
1212 /* for configuration palette (not yet supported) */
1213 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1215   /* return entire set... */
1216   return [identifierToItem allKeys];
1219 /* optional and unneeded */
1220 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1221 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1222 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1224 @end  /* EmacsToolbar */
1228 /* ==========================================================================
1230     Tooltip: class implementation
1232    ========================================================================== */
1234 /* Needed because NeXTstep does not provide enough control over tooltip
1235    display. */
1236 @implementation EmacsTooltip
1238 - init
1240   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1241                                             blue: 0.792 alpha: 0.95];
1242   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1243   NSFont *sfont = [font screenFont];
1244   int height = [sfont ascender] - [sfont descender];
1245 /*[font boundingRectForFont].size.height; */
1246   NSRect r = NSMakeRect (0, 0, 100, height+6);
1248   textField = [[NSTextField alloc] initWithFrame: r];
1249   [textField setFont: font];
1250   [textField setBackgroundColor: col];
1252   [textField setEditable: NO];
1253   [textField setSelectable: NO];
1254   [textField setBordered: NO];
1255   [textField setBezeled: NO];
1256   [textField setDrawsBackground: YES];
1258   win = [[NSWindow alloc]
1259             initWithContentRect: [textField frame]
1260                       styleMask: 0
1261                         backing: NSBackingStoreBuffered
1262                           defer: YES];
1263   [win setHasShadow: YES];
1264   [win setReleasedWhenClosed: NO];
1265   [win setDelegate: self];
1266   [[win contentView] addSubview: textField];
1267 /*  [win setBackgroundColor: col]; */
1268   [win setOpaque: NO];
1270   return self;
1273 - (void) dealloc
1275   [win close];
1276   [win release];
1277   [textField release];
1278   [super dealloc];
1281 - (void) setText: (char *)text
1283   NSString *str = [NSString stringWithUTF8String: text];
1284   NSRect r  = [textField frame];
1285   NSSize tooltipDims;
1287   [textField setStringValue: str];
1288   tooltipDims = [[textField cell] cellSize];
1290   r.size.width = tooltipDims.width;
1291   r.size.height = tooltipDims.height;
1292   [textField setFrame: r];
1295 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1297   NSRect wr = [win frame];
1299   wr.origin = NSMakePoint (x, y);
1300   wr.size = [textField frame].size;
1302   [win setFrame: wr display: YES];
1303   [win orderFront: self];
1304   [win display];
1305   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1306                                          selector: @selector (hide)
1307                                          userInfo: nil repeats: NO];
1308   [timer retain];
1311 - (void) hide
1313   [win close];
1314   if (timer != nil)
1315     {
1316       if ([timer isValid])
1317         [timer invalidate];
1318       [timer release];
1319       timer = nil;
1320     }
1323 - (BOOL) isActive
1325   return timer != nil;
1328 - (NSRect) frame
1330   return [textField frame];
1333 @end  /* EmacsTooltip */
1337 /* ==========================================================================
1339     Popup Dialog: implementing functions
1341    ========================================================================== */
1343 struct Popdown_data
1345   NSAutoreleasePool *pool;
1346   EmacsDialogPanel *dialog;
1349 static Lisp_Object
1350 pop_down_menu (Lisp_Object arg)
1352   struct Popdown_data *unwind_data = XSAVE_POINTER (arg, 0);
1354   block_input ();
1355   if (popup_activated_flag)
1356     {
1357       EmacsDialogPanel *panel = unwind_data->dialog;
1358       popup_activated_flag = 0;
1359       [panel close];
1360       [unwind_data->pool release];
1361       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1362     }
1364   xfree (unwind_data);
1365   unblock_input ();
1367   return Qnil;
1371 Lisp_Object
1372 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1374   id dialog;
1375   Lisp_Object window, tem, title;
1376   struct frame *f;
1377   NSPoint p;
1378   BOOL isQ;
1379   NSAutoreleasePool *pool;
1381   NSTRACE (x-popup-dialog);
1383   check_ns ();
1385   isQ = NILP (header);
1387   if (EQ (position, Qt)
1388       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1389                                || EQ (XCAR (position), Qtool_bar))))
1390     {
1391       window = selected_window;
1392     }
1393   else if (CONSP (position))
1394     {
1395       Lisp_Object tem;
1396       tem = Fcar (position);
1397       if (XTYPE (tem) == Lisp_Cons)
1398         window = Fcar (Fcdr (position));
1399       else
1400         {
1401           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1402           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1403         }
1404     }
1405   else if (WINDOWP (position) || FRAMEP (position))
1406     {
1407       window = position;
1408     }
1409   else
1410     window = Qnil;
1412   if (FRAMEP (window))
1413     f = XFRAME (window);
1414   else if (WINDOWP (window))
1415     {
1416       CHECK_LIVE_WINDOW (window);
1417       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1418     }
1419   else
1420     CHECK_WINDOW (window);
1422   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1423   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1425   title = Fcar (contents);
1426   CHECK_STRING (title);
1428   if (NILP (Fcar (Fcdr (contents))))
1429     /* No buttons specified, add an "Ok" button so users can pop down
1430        the dialog.  */
1431     contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1433   block_input ();
1434   pool = [[NSAutoreleasePool alloc] init];
1435   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1436                                            isQuestion: isQ];
1438   {
1439     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1440     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1442     unwind_data->pool = pool;
1443     unwind_data->dialog = dialog;
1445     record_unwind_protect (pop_down_menu, make_save_pointer (unwind_data));
1446     popup_activated_flag = 1;
1447     tem = [dialog runDialogAt: p];
1448     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1449   }
1451   unblock_input ();
1453   return tem;
1457 /* ==========================================================================
1459     Popup Dialog: class implementation
1461    ========================================================================== */
1463 @interface FlippedView : NSView
1466 @end
1468 @implementation FlippedView
1469 - (BOOL)isFlipped
1471   return YES;
1473 @end
1475 @implementation EmacsDialogPanel
1477 #define SPACER          8.0
1478 #define ICONSIZE        64.0
1479 #define TEXTHEIGHT      20.0
1480 #define MINCELLWIDTH    90.0
1482 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1483               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1485   NSSize spacing = {SPACER, SPACER};
1486   NSRect area;
1487   id cell;
1488   NSImageView *imgView;
1489   FlippedView *contentView;
1490   NSImage *img;
1492   dialog_return   = Qundefined;
1493   button_values   = NULL;
1494   area.origin.x   = 3*SPACER;
1495   area.origin.y   = 2*SPACER;
1496   area.size.width = ICONSIZE;
1497   area.size.height= ICONSIZE;
1498   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1499   [img setScalesWhenResized: YES];
1500   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1501   imgView = [[NSImageView alloc] initWithFrame: area];
1502   [imgView setImage: img];
1503   [imgView setEditable: NO];
1504   [img autorelease];
1505   [imgView autorelease];
1507   aStyle = NSTitledWindowMask;
1508   flag = YES;
1509   rows = 0;
1510   cols = 1;
1511   [super initWithContentRect: contentRect styleMask: aStyle
1512                      backing: backingType defer: flag];
1513   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1514   [contentView autorelease];
1516   [self setContentView: contentView];
1518   [[self contentView] setAutoresizesSubviews: YES];
1520   [[self contentView] addSubview: imgView];
1521   [self setTitle: @""];
1523   area.origin.x   += ICONSIZE+2*SPACER;
1524 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1525   area.size.width = 400;
1526   area.size.height= TEXTHEIGHT;
1527   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1528   [[self contentView] addSubview: command];
1529   [command setStringValue: ns_app_name];
1530   [command setDrawsBackground: NO];
1531   [command setBezeled: NO];
1532   [command setSelectable: NO];
1533   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1535 /*  area.origin.x   = ICONSIZE+2*SPACER;
1536   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1537   area.size.width = 400;
1538   area.size.height= 2;
1539   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1540   [[self contentView] addSubview: tem];
1541   [tem setTitlePosition: NSNoTitle];
1542   [tem setAutoresizingMask: NSViewWidthSizable];*/
1544 /*  area.origin.x = ICONSIZE+2*SPACER; */
1545   area.origin.y += TEXTHEIGHT+SPACER;
1546   area.size.width = 400;
1547   area.size.height= TEXTHEIGHT;
1548   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1549   [[self contentView] addSubview: title];
1550   [title setDrawsBackground: NO];
1551   [title setBezeled: NO];
1552   [title setSelectable: NO];
1553   [title setFont: [NSFont systemFontOfSize: 11.0]];
1555   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1556   [cell setBordered: NO];
1557   [cell setEnabled: NO];
1558   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1559   [cell setBezelStyle: NSRoundedBezelStyle];
1561   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1562                                       mode: NSHighlightModeMatrix
1563                                  prototype: cell
1564                               numberOfRows: 0
1565                            numberOfColumns: 1];
1566   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1567                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1568   [matrix setIntercellSpacing: spacing];
1569   [matrix autorelease];
1571   [[self contentView] addSubview: matrix];
1572   [self setOneShot: YES];
1573   [self setReleasedWhenClosed: YES];
1574   [self setHidesOnDeactivate: YES];
1575   [self setStyleMask:
1576           NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask];
1578   return self;
1582 - (BOOL)windowShouldClose: (id)sender
1584   window_closed = YES;
1585   [NSApp stop:self];
1586   return NO;
1589 - (void)dealloc
1591   xfree (button_values);
1592   [super dealloc];
1595 - (void)process_dialog: (Lisp_Object) list
1597   Lisp_Object item, lst = list;
1598   int row = 0;
1599   int buttons = 0, btnnr = 0;
1601   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1602     {
1603       item = XCAR (list);
1604       if (XTYPE (item) == Lisp_Cons)
1605         ++buttons;
1606     }
1608   if (buttons > 0)
1609     button_values = (Lisp_Object *) xmalloc (buttons * sizeof (*button_values));
1611   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1612     {
1613       item = XCAR (list);
1614       if (XTYPE (item) == Lisp_String)
1615         {
1616           [self addString: SSDATA (item) row: row++];
1617         }
1618       else if (XTYPE (item) == Lisp_Cons)
1619         {
1620           button_values[btnnr] = XCDR (item);
1621           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1622           ++btnnr;
1623         }
1624       else if (NILP (item))
1625         {
1626           [self addSplit];
1627           row = 0;
1628         }
1629     }
1633 - (void)addButton: (char *)str value: (int)tag row: (int)row
1635   id cell;
1637   if (row >= rows)
1638     {
1639       [matrix addRow];
1640       rows++;
1641     }
1642   cell = [matrix cellAtRow: row column: cols-1];
1643   [cell setTarget: self];
1644   [cell setAction: @selector (clicked: )];
1645   [cell setTitle: [NSString stringWithUTF8String: str]];
1646   [cell setTag: tag];
1647   [cell setBordered: YES];
1648   [cell setEnabled: YES];
1652 - (void)addString: (char *)str row: (int)row
1654   id cell;
1656   if (row >= rows)
1657     {
1658       [matrix addRow];
1659       rows++;
1660     }
1661   cell = [matrix cellAtRow: row column: cols-1];
1662   [cell setTitle: [NSString stringWithUTF8String: str]];
1663   [cell setBordered: YES];
1664   [cell setEnabled: NO];
1668 - (void)addSplit
1670   [matrix addColumn];
1671   cols++;
1675 - (void)clicked: sender
1677   NSArray *sellist = nil;
1678   EMACS_INT seltag;
1680   sellist = [sender selectedCells];
1681   if ([sellist count] < 1)
1682     return;
1684   seltag = [[sellist objectAtIndex: 0] tag];
1685   dialog_return = button_values[seltag];
1686   [NSApp stop:self];
1690 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1692   Lisp_Object head;
1693   [super init];
1695   if (XTYPE (contents) == Lisp_Cons)
1696     {
1697       head = Fcar (contents);
1698       [self process_dialog: Fcdr (contents)];
1699     }
1700   else
1701     head = contents;
1703   if (XTYPE (head) == Lisp_String)
1704       [title setStringValue:
1705                  [NSString stringWithUTF8String: SSDATA (head)]];
1706   else if (isQ == YES)
1707       [title setStringValue: @"Question"];
1708   else
1709       [title setStringValue: @"Information"];
1711   {
1712     int i;
1713     NSRect r, s, t;
1715     if (cols == 1 && rows > 1)  /* Never told where to split */
1716       {
1717         [matrix addColumn];
1718         for (i = 0; i < rows/2; i++)
1719           {
1720             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1721                       atRow: i column: 1];
1722             [matrix removeRow: (rows+1)/2];
1723           }
1724       }
1726     [matrix sizeToFit];
1727     {
1728       NSSize csize = [matrix cellSize];
1729       if (csize.width < MINCELLWIDTH)
1730         {
1731           csize.width = MINCELLWIDTH;
1732           [matrix setCellSize: csize];
1733           [matrix sizeToCells];
1734         }
1735     }
1737     [title sizeToFit];
1738     [command sizeToFit];
1740     t = [matrix frame];
1741     r = [title frame];
1742     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1743       {
1744         t.origin.x   = r.origin.x;
1745         t.size.width = r.size.width;
1746       }
1747     r = [command frame];
1748     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1749       {
1750         t.origin.x   = r.origin.x;
1751         t.size.width = r.size.width;
1752       }
1754     r = [self frame];
1755     s = [(NSView *)[self contentView] frame];
1756     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1757     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1758     [self setFrame: r display: NO];
1759   }
1761   return self;
1766 - (void)timeout_handler: (NSTimer *)timedEntry
1768   NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1769                             location: NSMakePoint (0, 0)
1770                        modifierFlags: 0
1771                            timestamp: 0
1772                         windowNumber: [[NSApp mainWindow] windowNumber]
1773                              context: [NSApp context]
1774                              subtype: 0
1775                                data1: 0
1776                                data2: 0];
1778   timer_fired = YES;
1779   /* We use sto because stopModal/abortModal out of the main loop does not
1780      seem to work in 10.6.  But as we use stop we must send a real event so
1781      the stop is seen and acted upon.  */
1782   [NSApp stop:self];
1783   [NSApp postEvent: nxev atStart: NO];
1786 - (Lisp_Object)runDialogAt: (NSPoint)p
1788   Lisp_Object ret = Qundefined;
1790   while (popup_activated_flag)
1791     {
1792       NSTimer *tmo = nil;
1793       EMACS_TIME next_time = timer_check ();
1795       if (EMACS_TIME_VALID_P (next_time))
1796         {
1797           double time = EMACS_TIME_TO_DOUBLE (next_time);
1798           tmo = [NSTimer timerWithTimeInterval: time
1799                                         target: self
1800                                       selector: @selector (timeout_handler:)
1801                                       userInfo: 0
1802                                        repeats: NO];
1803           [[NSRunLoop currentRunLoop] addTimer: tmo
1804                                        forMode: NSModalPanelRunLoopMode];
1805         }
1806       timer_fired = NO;
1807       dialog_return = Qundefined;
1808       [NSApp runModalForWindow: self];
1809       ret = dialog_return;
1810       if (! timer_fired)
1811         {
1812           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1813           break;
1814         }
1815     }
1817   if (EQ (ret, Qundefined) && window_closed)
1818     /* Make close button pressed equivalent to C-g.  */
1819     Fsignal (Qquit, Qnil);
1821   return ret;
1824 @end
1827 /* ==========================================================================
1829     Lisp definitions
1831    ========================================================================== */
1833 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1834        doc: /* Cause the NS menu to be re-calculated.  */)
1835      (void)
1837   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1838   return Qnil;
1842 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1843        doc: /* Pop up a dialog box and return user's selection.
1844 POSITION specifies which frame to use.
1845 This is normally a mouse button event or a window or frame.
1846 If POSITION is t, it means to use the frame the mouse is on.
1847 The dialog box appears in the middle of the specified frame.
1849 CONTENTS specifies the alternatives to display in the dialog box.
1850 It is a list of the form (DIALOG ITEM1 ITEM2...).
1851 Each ITEM is a cons cell (STRING . VALUE).
1852 The return value is VALUE from the chosen item.
1854 An ITEM may also be just a string--that makes a nonselectable item.
1855 An ITEM may also be nil--that means to put all preceding items
1856 on the left of the dialog box and all following items on the right.
1857 \(By default, approximately half appear on each side.)
1859 If HEADER is non-nil, the frame title for the box is "Information",
1860 otherwise it is "Question".
1862 If the user gets rid of the dialog box without making a valid choice,
1863 for instance using the window manager, then this produces a quit and
1864 `x-popup-dialog' does not return.  */)
1865      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1867   return ns_popup_dialog (position, contents, header);
1870 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1871        doc: /* Return t if a menu or popup dialog is active.  */)
1872      (void)
1874   return popup_activated () ? Qt : Qnil;
1877 /* ==========================================================================
1879     Lisp interface declaration
1881    ========================================================================== */
1883 void
1884 syms_of_nsmenu (void)
1886 #ifndef NS_IMPL_COCOA
1887   /* Don't know how to keep track of this in Next/Open/Gnustep.  Always
1888      update menus there.  */
1889   trackingMenu = 1;
1890 #endif
1891   defsubr (&Sx_popup_dialog);
1892   defsubr (&Sns_reset_menu);
1893   defsubr (&Smenu_or_popup_active_p);
1895   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1896   staticpro (&Qdebug_on_next_call);