Commit file missing from previous change
[emacs.git] / src / nsmenu.m
blobbaa683941f8571d2034e0c9a267a986e555d85f3
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 /* Supposed to discard menubar and free storage.  Since we share the
92    menubar among frames and update its context for the focused window,
93    there is nothing to do here. */
94 void
95 free_frame_menubar (struct frame *f)
97   return;
102 popup_activated (void)
104   return popup_activated_flag;
108 /* --------------------------------------------------------------------------
109     Update menubar.  Three cases:
110     1) ! deep_p, submenu = nil: Fresh switch onto a frame -- either set up
111        just top-level menu strings (OS X), or goto case (2) (GNUstep).
112     2) deep_p, submenu = nil: Recompute all submenus.
113     3) deep_p, submenu = non-nil: Update contents of a single submenu.
114    -------------------------------------------------------------------------- */
115 void
116 ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu)
118   NSAutoreleasePool *pool;
119   id menu = [NSApp mainMenu];
120   static EmacsMenu *last_submenu = nil;
121   BOOL needsSet = NO;
122   bool owfi;
123   Lisp_Object items;
124   widget_value *wv, *first_wv, *prev_wv = 0;
125   int i;
127 #if NSMENUPROFILE
128   struct timeb tb;
129   long t;
130 #endif
132   NSTRACE (ns_update_menubar);
134   if (f != SELECTED_FRAME ())
135       return;
136   XSETFRAME (Vmenu_updating_frame, f);
137 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
139   block_input ();
140   pool = [[NSAutoreleasePool alloc] init];
142   /* Menu may have been created automatically; if so, discard it. */
143   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
144     {
145       [menu release];
146       menu = nil;
147     }
149   if (menu == nil)
150     {
151       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
152       needsSet = YES;
153     }
154   else
155     {  /* close up anything on there */
156       id attMenu = [menu attachedMenu];
157       if (attMenu != nil)
158         [attMenu close];
159     }
161 #if NSMENUPROFILE
162   ftime (&tb);
163   t = -(1000*tb.time+tb.millitm);
164 #endif
166 #ifdef NS_IMPL_GNUSTEP
167   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
168 #endif
170   if (deep_p)
171     {
172       /* Fully parse one or more of the submenus. */
173       int n = 0;
174       int *submenu_start, *submenu_end;
175       bool *submenu_top_level_items;
176       int *submenu_n_panes;
177       struct buffer *prev = current_buffer;
178       Lisp_Object buffer;
179       ptrdiff_t specpdl_count = SPECPDL_INDEX ();
180       int previous_menu_items_used = f->menu_bar_items_used;
181       Lisp_Object *previous_items
182         = alloca (previous_menu_items_used * sizeof *previous_items);
184       /* lisp preliminaries */
185       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->contents;
186       specbind (Qinhibit_quit, Qt);
187       specbind (Qdebug_on_next_call, Qnil);
188       record_unwind_save_match_data ();
189       if (NILP (Voverriding_local_map_menu_flag))
190         {
191           specbind (Qoverriding_terminal_local_map, Qnil);
192           specbind (Qoverriding_local_map, Qnil);
193         }
194       set_buffer_internal_1 (XBUFFER (buffer));
196       /* TODO: for some reason this is not needed in other terms,
197            but some menu updates call Info-extract-pointer which causes
198            abort-on-error if waiting-for-input.  Needs further investigation. */
199       owfi = waiting_for_input;
200       waiting_for_input = 0;
202       /* lucid hook and possible reset */
203       safe_run_hooks (Qactivate_menubar_hook);
204       if (! NILP (Vlucid_menu_bar_dirty_flag))
205         call0 (Qrecompute_lucid_menubar);
206       safe_run_hooks (Qmenu_bar_update_hook);
207       fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
209       /* Now ready to go */
210       items = FRAME_MENU_BAR_ITEMS (f);
212       /* Save the frame's previous menu bar contents data */
213       if (previous_menu_items_used)
214         memcpy (previous_items, aref_addr (f->menu_bar_vector, 0),
215                 previous_menu_items_used * sizeof (Lisp_Object));
217       /* parse stage 1: extract from lisp */
218       save_menu_items ();
220       menu_items = f->menu_bar_vector;
221       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
222       submenu_start = alloca (ASIZE (items) * sizeof *submenu_start);
223       submenu_end = alloca (ASIZE (items) * sizeof *submenu_end);
224       submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes);
225       submenu_top_level_items = alloca (ASIZE (items)
226                                         * sizeof *submenu_top_level_items);
227       init_menu_items ();
228       for (i = 0; i < ASIZE (items); i += 4)
229         {
230           Lisp_Object key, string, maps;
232           key = AREF (items, i);
233           string = AREF (items, i + 1);
234           maps = AREF (items, i + 2);
235           if (NILP (string))
236             break;
238           /* FIXME: we'd like to only parse the needed submenu, but this
239                was causing crashes in the _common parsing code.. need to make
240                sure proper initialization done.. */
241 /*        if (submenu && strcmp ([[submenu title] UTF8String], SSDATA (string)))
242              continue; */
244           submenu_start[i] = menu_items_used;
246           menu_items_n_panes = 0;
247           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
248           submenu_n_panes[i] = menu_items_n_panes;
249           submenu_end[i] = menu_items_used;
250           n++;
251         }
253       finish_menu_items ();
254       waiting_for_input = owfi;
257       if (submenu && n == 0)
258         {
259           /* should have found a menu for this one but didn't */
260           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
261                   [[submenu title] UTF8String]);
262           discard_menu_items ();
263           unbind_to (specpdl_count, Qnil);
264           [pool release];
265           unblock_input ();
266           return;
267         }
269       /* parse stage 2: insert into lucid 'widget_value' structures
270          [comments in other terms say not to evaluate lisp code here] */
271       wv = xmalloc_widget_value ();
272       wv->name = "menubar";
273       wv->value = 0;
274       wv->enabled = 1;
275       wv->button_type = BUTTON_TYPE_NONE;
276       wv->help = Qnil;
277       first_wv = wv;
279       for (i = 0; i < 4*n; i += 4)
280         {
281           menu_items_n_panes = submenu_n_panes[i];
282           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
283                                       submenu_top_level_items[i]);
284           if (prev_wv)
285             prev_wv->next = wv;
286           else
287             first_wv->contents = wv;
288           /* Don't set wv->name here; GC during the loop might relocate it.  */
289           wv->enabled = 1;
290           wv->button_type = BUTTON_TYPE_NONE;
291           prev_wv = wv;
292         }
294       set_buffer_internal_1 (prev);
296       /* Compare the new menu items with previous, and leave off if no change */
297       /* FIXME: following other terms here, but seems like this should be
298            done before parse stage 2 above, since its results aren't used */
299       if (previous_menu_items_used
300           && (!submenu || (submenu && submenu == last_submenu))
301           && menu_items_used == previous_menu_items_used)
302         {
303           for (i = 0; i < previous_menu_items_used; i++)
304             /* FIXME: this ALWAYS fails on Buffers menu items.. something
305                  about their strings causes them to change every time, so we
306                  double-check failures */
307             if (!EQ (previous_items[i], AREF (menu_items, i)))
308               if (!(STRINGP (previous_items[i])
309                     && STRINGP (AREF (menu_items, i))
310                     && !strcmp (SSDATA (previous_items[i]),
311                                 SSDATA (AREF (menu_items, i)))))
312                   break;
313           if (i == previous_menu_items_used)
314             {
315               /* No change.. */
317 #if NSMENUPROFILE
318               ftime (&tb);
319               t += 1000*tb.time+tb.millitm;
320               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
321 #endif
323               free_menubar_widget_value_tree (first_wv);
324               discard_menu_items ();
325               unbind_to (specpdl_count, Qnil);
326               [pool release];
327               unblock_input ();
328               return;
329             }
330         }
331       /* The menu items are different, so store them in the frame */
332       /* FIXME: this is not correct for single-submenu case */
333       fset_menu_bar_vector (f, menu_items);
334       f->menu_bar_items_used = menu_items_used;
336       /* Calls restore_menu_items, etc., as they were outside */
337       unbind_to (specpdl_count, Qnil);
339       /* Parse stage 2a: now GC cannot happen during the lifetime of the
340          widget_value, so it's safe to store data from a Lisp_String */
341       wv = first_wv->contents;
342       for (i = 0; i < ASIZE (items); i += 4)
343         {
344           Lisp_Object string;
345           string = AREF (items, i + 1);
346           if (NILP (string))
347             break;
349           wv->name = SSDATA (string);
350           update_submenu_strings (wv->contents);
351           wv = wv->next;
352         }
354       /* Now, update the NS menu; if we have a submenu, use that, otherwise
355          create a new menu for each sub and fill it. */
356       if (submenu)
357         {
358           const char *submenuTitle = [[submenu title] UTF8String];
359           for (wv = first_wv->contents; wv; wv = wv->next)
360             {
361               if (!strcmp (submenuTitle, wv->name))
362                 {
363                   [submenu fillWithWidgetValue: wv->contents];
364                   last_submenu = submenu;
365                   break;
366                 }
367             }
368         }
369       else
370         {
371           [menu fillWithWidgetValue: first_wv->contents];
372         }
374     }
375   else
376     {
377       static int n_previous_strings = 0;
378       static char previous_strings[100][10];
379       static struct frame *last_f = NULL;
380       int n;
381       Lisp_Object string;
383       wv = xmalloc_widget_value ();
384       wv->name = "menubar";
385       wv->value = 0;
386       wv->enabled = 1;
387       wv->button_type = BUTTON_TYPE_NONE;
388       wv->help = Qnil;
389       first_wv = wv;
391       /* Make widget-value tree w/ just the top level menu bar strings */
392       items = FRAME_MENU_BAR_ITEMS (f);
393       if (NILP (items))
394         {
395           free_menubar_widget_value_tree (first_wv);
396           [pool release];
397           unblock_input ();
398           return;
399         }
402       /* check if no change.. this mechanism is a bit rough, but ready */
403       n = ASIZE (items) / 4;
404       if (f == last_f && n_previous_strings == n)
405         {
406           for (i = 0; i<n; i++)
407             {
408               string = AREF (items, 4*i+1);
410               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
411                 continue;
412               if (NILP (string))
413                 {
414                   if (previous_strings[i][0])
415                     break;
416                   else
417                     continue;
418                 }
419               else if (memcmp (previous_strings[i], SDATA (string),
420                           min (10, SBYTES (string) + 1)))
421                 break;
422             }
424           if (i == n)
425             {
426               free_menubar_widget_value_tree (first_wv);
427               [pool release];
428               unblock_input ();
429               return;
430             }
431         }
433       [menu clear];
434       for (i = 0; i < ASIZE (items); i += 4)
435         {
436           string = AREF (items, i + 1);
437           if (NILP (string))
438             break;
440           if (n < 100)
441             memcpy (previous_strings[i/4], SDATA (string),
442                     min (10, SBYTES (string) + 1));
444           wv = xmalloc_widget_value ();
445           wv->name = SSDATA (string);
446           wv->value = 0;
447           wv->enabled = 1;
448           wv->button_type = BUTTON_TYPE_NONE;
449           wv->help = Qnil;
450           wv->call_data = (void *) (intptr_t) (-1);
452 #ifdef NS_IMPL_COCOA
453           /* we'll update the real copy under app menu when time comes */
454           if (!strcmp ("Services", wv->name))
455             {
456               /* but we need to make sure it will update on demand */
457               [svcsMenu setFrame: f];
458             }
459           else
460 #endif
461           [menu addSubmenuWithTitle: wv->name forFrame: f];
463           if (prev_wv)
464             prev_wv->next = wv;
465           else
466             first_wv->contents = wv;
467           prev_wv = wv;
468         }
470       last_f = f;
471       if (n < 100)
472         n_previous_strings = n;
473       else
474         n_previous_strings = 0;
476     }
477   free_menubar_widget_value_tree (first_wv);
480 #if NSMENUPROFILE
481   ftime (&tb);
482   t += 1000*tb.time+tb.millitm;
483   fprintf (stderr, "Menu update took %ld msec.\n", t);
484 #endif
486   /* set main menu */
487   if (needsSet)
488     [NSApp setMainMenu: menu];
490   [pool release];
491   unblock_input ();
496 /* Main emacs core entry point for menubar menus: called to indicate that the
497    frame's menus have changed, and the *step representation should be updated
498    from Lisp. */
499 void
500 set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
502   ns_update_menubar (f, deep_p, nil);
505 void
506 x_activate_menubar (struct frame *f)
508   NSArray *a = [[NSApp mainMenu] itemArray];
509   /* Update each submenu separately so ns_update_menubar doesn't reset
510      the delegate.  */
511   int i = 0;
512   while (i < [a count])
513     {
514       EmacsMenu *menu = (EmacsMenu *)[[a objectAtIndex:i] submenu];
515       const char *title = [[menu title] UTF8String];
516       if (strcmp (title, ns_get_pending_menu_title ()) == 0)
517         {
518           ns_update_menubar (f, true, menu);
519           break;
520         }
521       ++i;
522     }
523   ns_check_pending_open_menu ();
529 /* ==========================================================================
531     Menu: class implementation
533    ========================================================================== */
536 /* Menu that can define itself from Emacs "widget_value"s and will lazily
537    update itself when user clicked.  Based on Carbon/AppKit implementation
538    by Yamamoto Mitsuharu. */
539 @implementation EmacsMenu
541 /* override designated initializer */
542 - initWithTitle: (NSString *)title
544   if ((self = [super initWithTitle: title]))
545     [self setAutoenablesItems: NO];
546   return self;
550 /* used for top-level */
551 - initWithTitle: (NSString *)title frame: (struct frame *)f
553   [self initWithTitle: title];
554   frame = f;
555 #ifdef NS_IMPL_COCOA
556   [self setDelegate: self];
557 #endif
558   return self;
562 - (void)setFrame: (struct frame *)f
564   frame = f;
567 #ifdef NS_IMPL_COCOA
568 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
569 extern NSString *NSMenuDidBeginTrackingNotification;
570 #endif
571 #endif
573 #ifdef NS_IMPL_COCOA
574 -(void)trackingNotification:(NSNotification *)notification
576   /* Update menu in menuNeedsUpdate only while tracking menus.  */
577   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
578                   ? 1 : 0);
581 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
582 - (void)menuWillOpen:(NSMenu *)menu
584   ns_check_menu_open (menu);
586 #endif
588 #endif
590 /* delegate method called when a submenu is being opened: run a 'deep' call
591    to set_frame_menubar */
592 - (void)menuNeedsUpdate: (NSMenu *)menu
594   if (!FRAME_LIVE_P (frame))
595     return;
597   /* Cocoa/Carbon will request update on every keystroke
598      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
599      since key equivalents are handled through emacs.
600      On Leopard, even keystroke events generate SystemDefined event.
601      Third-party applications that enhance mouse / trackpad
602      interaction, or also VNC/Remote Desktop will send events
603      of type AppDefined rather than SysDefined.
604      Menus will fail to show up if they haven't been initialized.
605      AppDefined events may lack timing data.
607      Thus, we rely on the didBeginTrackingNotification notification
608      as above to indicate the need for updates.
609      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
610      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
611   */
612   if (trackingMenu == 0)
613     return;
614 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
615 #if ! defined(NS_IMPL_COCOA) || \
616   MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
617   /* Don't know how to do this for anything other than OSX >= 10.5
618      This is wrong, as it might run Lisp code in the event loop.  */
619   ns_update_menubar (frame, true, self);
620 #endif
624 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
626   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
627       && FRAME_NS_VIEW (SELECTED_FRAME ()))
628     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
629   return YES;
633 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
634    into an accelerator string.  We are only able to display a single character
635    for an accelerator, together with an optional modifier combination.  (Under
636    Carbon more control was possible, but in Cocoa multi-char strings passed to
637    NSMenuItem get ignored.  For now we try to display a super-single letter
638    combo, and return the others as strings to be appended to the item title.
639    (This is signaled by setting keyEquivModMask to 0 for now.) */
640 -(NSString *)parseKeyEquiv: (const char *)key
642   const char *tpos = key;
643   keyEquivModMask = NSCommandKeyMask;
645   if (!key || !strlen (key))
646     return @"";
648   while (*tpos == ' ' || *tpos == '(')
649     tpos++;
650   if ((*tpos == 's') && (*(tpos+1) == '-'))
651     {
652       return [NSString stringWithFormat: @"%c", tpos[2]];
653     }
654   keyEquivModMask = 0; /* signal */
655   return [NSString stringWithUTF8String: tpos];
659 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
661   NSMenuItem *item;
662   widget_value *wv = (widget_value *)wvptr;
664   if (menu_separator_name_p (wv->name))
665     {
666       item = [NSMenuItem separatorItem];
667       [self addItem: item];
668     }
669   else
670     {
671       NSString *title, *keyEq;
672       title = [NSString stringWithUTF8String: wv->name];
673       if (title == nil)
674         title = @"< ? >";  /* (get out in the open so we know about it) */
676       keyEq = [self parseKeyEquiv: wv->key];
677 #ifdef NS_IMPL_COCOA
678       /* OS X just ignores modifier strings longer than one character */
679       if (keyEquivModMask == 0)
680         title = [title stringByAppendingFormat: @" (%@)", keyEq];
681 #endif
683       item = [self addItemWithTitle: (NSString *)title
684                              action: @selector (menuDown:)
685                       keyEquivalent: keyEq];
686       [item setKeyEquivalentModifierMask: keyEquivModMask];
688       [item setEnabled: wv->enabled];
690       /* Draw radio buttons and tickboxes */
691       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
692                            wv->button_type == BUTTON_TYPE_RADIO))
693         [item setState: NSOnState];
694       else
695         [item setState: NSOffState];
697       [item setTag: (NSInteger)wv->call_data];
698     }
700   return item;
704 /* convenience */
705 -(void)clear
707   int n;
709   for (n = [self numberOfItems]-1; n >= 0; n--)
710     {
711       NSMenuItem *item = [self itemAtIndex: n];
712       NSString *title = [item title];
713       if (([title length] == 0  /* OSX 10.5 */
714            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
715            || [@"Apple" isEqualToString: title]) /* older */
716           && ![item isSeparatorItem])
717         continue;
718       [self removeItemAtIndex: n];
719     }
723 - (void)fillWithWidgetValue: (void *)wvptr
725   widget_value *wv = (widget_value *)wvptr;
727   /* clear existing contents */
728   [self setMenuChangedMessagesEnabled: NO];
729   [self clear];
731   /* add new contents */
732   for (; wv != NULL; wv = wv->next)
733     {
734       NSMenuItem *item = [self addItemWithWidgetValue: wv];
736       if (wv->contents)
737         {
738           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
740           [self setSubmenu: submenu forItem: item];
741           [submenu fillWithWidgetValue: wv->contents];
742           [submenu release];
743           [item setAction: nil];
744         }
745     }
747   [self setMenuChangedMessagesEnabled: YES];
748 #ifdef NS_IMPL_GNUSTEP
749   if ([[self window] isVisible])
750     [self sizeToFit];
751 #endif
755 /* adds an empty submenu and returns it */
756 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
758   NSString *titleStr = [NSString stringWithUTF8String: title];
759   NSMenuItem *item = [self addItemWithTitle: titleStr
760                                      action: nil /*@selector (menuDown:) */
761                               keyEquivalent: @""];
762   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
763   [self setSubmenu: submenu forItem: item];
764   [submenu release];
765   return submenu;
768 /* run a menu in popup mode */
769 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
770                  keymaps: (bool)keymaps
772   EmacsView *view = FRAME_NS_VIEW (f);
773   NSEvent *e, *event;
774   long retVal;
776 /*   p = [view convertPoint:p fromView: nil]; */
777   p.y = NSHeight ([view frame]) - p.y;
778   e = [[view window] currentEvent];
779    event = [NSEvent mouseEventWithType: NSRightMouseDown
780                               location: p
781                          modifierFlags: 0
782                              timestamp: [e timestamp]
783                           windowNumber: [[view window] windowNumber]
784                                context: [e context]
785                            eventNumber: 0/*[e eventNumber] */
786                             clickCount: 1
787                               pressure: 0];
789   context_menu_value = -1;
790   [NSMenu popUpContextMenu: self withEvent: event forView: view];
791   retVal = context_menu_value;
792   context_menu_value = 0;
793   return retVal > 0
794       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
795       : Qnil;
798 @end  /* EmacsMenu */
802 /* ==========================================================================
804     Context Menu: implementing functions
806    ========================================================================== */
808 Lisp_Object
809 ns_menu_show (FRAME_PTR f, int x, int y, bool for_click, bool keymaps,
810               Lisp_Object title, const char **error)
812   EmacsMenu *pmenu;
813   NSPoint p;
814   Lisp_Object tem;
815   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
816   widget_value *wv, *first_wv = 0;
818   p.x = x; p.y = y;
820   /* now parse stage 2 as in ns_update_menubar */
821   wv = xmalloc_widget_value ();
822   wv->name = "contextmenu";
823   wv->value = 0;
824   wv->enabled = 1;
825   wv->button_type = BUTTON_TYPE_NONE;
826   wv->help = Qnil;
827   first_wv = wv;
829 #if 0
830   /* FIXME: a couple of one-line differences prevent reuse */
831   wv = digest_single_submenu (0, menu_items_used, 0);
832 #else
833   {
834   widget_value *save_wv = 0, *prev_wv = 0;
835   widget_value **submenu_stack
836     = alloca (menu_items_used * sizeof *submenu_stack);
837 /*   Lisp_Object *subprefix_stack
838        = alloca (menu_items_used * sizeof *subprefix_stack); */
839   int submenu_depth = 0;
840   int first_pane = 1;
841   int i;
843   /* Loop over all panes and items, filling in the tree.  */
844   i = 0;
845   while (i < menu_items_used)
846     {
847       if (EQ (AREF (menu_items, i), Qnil))
848         {
849           submenu_stack[submenu_depth++] = save_wv;
850           save_wv = prev_wv;
851           prev_wv = 0;
852           first_pane = 1;
853           i++;
854         }
855       else if (EQ (AREF (menu_items, i), Qlambda))
856         {
857           prev_wv = save_wv;
858           save_wv = submenu_stack[--submenu_depth];
859           first_pane = 0;
860           i++;
861         }
862       else if (EQ (AREF (menu_items, i), Qt)
863                && submenu_depth != 0)
864         i += MENU_ITEMS_PANE_LENGTH;
865       /* Ignore a nil in the item list.
866          It's meaningful only for dialog boxes.  */
867       else if (EQ (AREF (menu_items, i), Qquote))
868         i += 1;
869       else if (EQ (AREF (menu_items, i), Qt))
870         {
871           /* Create a new pane.  */
872           Lisp_Object pane_name, prefix;
873           const char *pane_string;
875           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
876           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
878 #ifndef HAVE_MULTILINGUAL_MENU
879           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
880             {
881               pane_name = ENCODE_MENU_STRING (pane_name);
882               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
883             }
884 #endif
885           pane_string = (NILP (pane_name)
886                          ? "" : SSDATA (pane_name));
887           /* If there is just one top-level pane, put all its items directly
888              under the top-level menu.  */
889           if (menu_items_n_panes == 1)
890             pane_string = "";
892           /* If the pane has a meaningful name,
893              make the pane a top-level menu item
894              with its items as a submenu beneath it.  */
895           if (!keymaps && strcmp (pane_string, ""))
896             {
897               wv = xmalloc_widget_value ();
898               if (save_wv)
899                 save_wv->next = wv;
900               else
901                 first_wv->contents = wv;
902               wv->name = pane_string;
903               if (keymaps && !NILP (prefix))
904                 wv->name++;
905               wv->value = 0;
906               wv->enabled = 1;
907               wv->button_type = BUTTON_TYPE_NONE;
908               wv->help = Qnil;
909               save_wv = wv;
910               prev_wv = 0;
911             }
912           else if (first_pane)
913             {
914               save_wv = wv;
915               prev_wv = 0;
916             }
917           first_pane = 0;
918           i += MENU_ITEMS_PANE_LENGTH;
919         }
920       else
921         {
922           /* Create a new item within current pane.  */
923           Lisp_Object item_name, enable, descrip, def, type, selected, help;
924           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
925           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
926           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
927           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
928           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
929           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
930           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
932 #ifndef HAVE_MULTILINGUAL_MENU
933           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
934             {
935               item_name = ENCODE_MENU_STRING (item_name);
936               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
937             }
939           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
940             {
941               descrip = ENCODE_MENU_STRING (descrip);
942               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
943             }
944 #endif /* not HAVE_MULTILINGUAL_MENU */
946           wv = xmalloc_widget_value ();
947           if (prev_wv)
948             prev_wv->next = wv;
949           else
950             save_wv->contents = wv;
951           wv->name = SSDATA (item_name);
952           if (!NILP (descrip))
953             wv->key = SSDATA (descrip);
954           wv->value = 0;
955           /* If this item has a null value,
956              make the call_data null so that it won't display a box
957              when the mouse is on it.  */
958           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
959           wv->enabled = !NILP (enable);
961           if (NILP (type))
962             wv->button_type = BUTTON_TYPE_NONE;
963           else if (EQ (type, QCtoggle))
964             wv->button_type = BUTTON_TYPE_TOGGLE;
965           else if (EQ (type, QCradio))
966             wv->button_type = BUTTON_TYPE_RADIO;
967           else
968             emacs_abort ();
970           wv->selected = !NILP (selected);
972           if (! STRINGP (help))
973             help = Qnil;
975           wv->help = help;
977           prev_wv = wv;
979           i += MENU_ITEMS_ITEM_LENGTH;
980         }
981     }
982   }
983 #endif
985   if (!NILP (title))
986     {
987       widget_value *wv_title = xmalloc_widget_value ();
988       widget_value *wv_sep = xmalloc_widget_value ();
990       /* Maybe replace this separator with a bitmap or owner-draw item
991          so that it looks better.  Having two separators looks odd.  */
992       wv_sep->name = "--";
993       wv_sep->next = first_wv->contents;
994       wv_sep->help = Qnil;
996 #ifndef HAVE_MULTILINGUAL_MENU
997       if (STRING_MULTIBYTE (title))
998         title = ENCODE_MENU_STRING (title);
999 #endif
1001       wv_title->name = SSDATA (title);
1002       wv_title->enabled = NO;
1003       wv_title->button_type = BUTTON_TYPE_NONE;
1004       wv_title->help = Qnil;
1005       wv_title->next = wv_sep;
1006       first_wv->contents = wv_title;
1007     }
1009   pmenu = [[EmacsMenu alloc] initWithTitle:
1010                                [NSString stringWithUTF8String: SSDATA (title)]];
1011   [pmenu fillWithWidgetValue: first_wv->contents];
1012   free_menubar_widget_value_tree (first_wv);
1013   unbind_to (specpdl_count, Qnil);
1015   popup_activated_flag = 1;
1016   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1017   popup_activated_flag = 0;
1018   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1020   return tem;
1024 /* ==========================================================================
1026     Toolbar: externally-called functions
1028    ========================================================================== */
1030 void
1031 free_frame_tool_bar (FRAME_PTR f)
1032 /* --------------------------------------------------------------------------
1033     Under NS we just hide the toolbar until it might be needed again.
1034    -------------------------------------------------------------------------- */
1036   block_input ();
1037   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1038   FRAME_TOOLBAR_HEIGHT (f) = 0;
1039   unblock_input ();
1042 void
1043 update_frame_tool_bar (FRAME_PTR f)
1044 /* --------------------------------------------------------------------------
1045     Update toolbar contents
1046    -------------------------------------------------------------------------- */
1048   int i;
1049   EmacsView *view = FRAME_NS_VIEW (f);
1050   NSWindow *window = [view window];
1051   EmacsToolbar *toolbar = [view toolbar];
1053   block_input ();
1054   [toolbar clearActive];
1056   /* update EmacsToolbar as in GtkUtils, build items list */
1057   for (i = 0; i < f->n_tool_bar_items; ++i)
1058     {
1059 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1060                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1062       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1063       int idx;
1064       ptrdiff_t img_id;
1065       struct image *img;
1066       Lisp_Object image;
1067       Lisp_Object helpObj;
1068       const char *helpText;
1070       /* If image is a vector, choose the image according to the
1071          button state.  */
1072       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1073       if (VECTORP (image))
1074         {
1075           /* NS toolbar auto-computes disabled and selected images */
1076           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1077           eassert (ASIZE (image) >= idx);
1078           image = AREF (image, idx);
1079         }
1080       else
1081         {
1082           idx = -1;
1083         }
1084       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1085       if (NILP (helpObj))
1086         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1087       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1089       /* Ignore invalid image specifications.  */
1090       if (!valid_image_p (image))
1091         {
1092           /* Don't log anything, GNUS makes invalid images all the time.  */
1093           continue;
1094         }
1096       img_id = lookup_image (f, image);
1097       img = IMAGE_FROM_ID (f, img_id);
1098       prepare_image_for_display (f, img);
1100       if (img->load_failed_p || img->pixmap == nil)
1101         {
1102           NSLog (@"Could not prepare toolbar image for display.");
1103           continue;
1104         }
1106       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1107                                enabled: enabled_p];
1108 #undef TOOLPROP
1109     }
1111   if (![toolbar isVisible])
1112       [toolbar setVisible: YES];
1114   if ([toolbar changed])
1115     {
1116       /* inform app that toolbar has changed */
1117       NSDictionary *dict = [toolbar configurationDictionary];
1118       NSMutableDictionary *newDict = [dict mutableCopy];
1119       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1120       id key;
1121       while ((key = [keys nextObject]) != nil)
1122         {
1123           NSObject *val = [dict objectForKey: key];
1124           if ([val isKindOfClass: [NSArray class]])
1125             {
1126               [newDict setObject:
1127                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1128                           forKey: key];
1129               break;
1130             }
1131         }
1132       [toolbar setConfigurationFromDictionary: newDict];
1133       [newDict release];
1134     }
1136   FRAME_TOOLBAR_HEIGHT (f) =
1137     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1138     - FRAME_NS_TITLEBAR_HEIGHT (f);
1139     if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1140       FRAME_TOOLBAR_HEIGHT (f) = 0;
1141     unblock_input ();
1145 /* ==========================================================================
1147     Toolbar: class implementation
1149    ========================================================================== */
1151 @implementation EmacsToolbar
1153 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1155   self = [super initWithIdentifier: identifier];
1156   emacsView = view;
1157   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1158   [self setSizeMode: NSToolbarSizeModeSmall];
1159   [self setDelegate: self];
1160   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1161   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1162   prevEnablement = enablement = 0L;
1163   return self;
1166 - (void)dealloc
1168   [prevIdentifiers release];
1169   [activeIdentifiers release];
1170   [identifierToItem release];
1171   [super dealloc];
1174 - (void) clearActive
1176   [prevIdentifiers release];
1177   prevIdentifiers = [activeIdentifiers copy];
1178   [activeIdentifiers removeAllObjects];
1179   prevEnablement = enablement;
1180   enablement = 0L;
1183 - (BOOL) changed
1185   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1186     enablement == prevEnablement ? NO : YES;
1189 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1190                         helpText: (const char *)help enabled: (BOOL)enabled
1192   /* 1) come up w/identifier */
1193   NSString *identifier
1194       = [NSString stringWithFormat: @"%u", [img hash]];
1196   /* 2) create / reuse item */
1197   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1198   if (item == nil)
1199     {
1200       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1201                autorelease];
1202       [item setImage: img];
1203       [item setToolTip: [NSString stringWithUTF8String: help]];
1204       [item setTarget: emacsView];
1205       [item setAction: @selector (toolbarClicked:)];
1206     }
1208   [item setTag: idx];
1209   [item setEnabled: enabled];
1211   /* 3) update state */
1212   [identifierToItem setObject: item forKey: identifier];
1213   [activeIdentifiers addObject: identifier];
1214   enablement = (enablement << 1) | (enabled == YES);
1217 /* This overrides super's implementation, which automatically sets
1218    all items to enabled state (for some reason). */
1219 - (void)validateVisibleItems { }
1222 /* delegate methods */
1224 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1225       itemForItemIdentifier: (NSString *)itemIdentifier
1226   willBeInsertedIntoToolbar: (BOOL)flag
1228   /* look up NSToolbarItem by identifier and return... */
1229   return [identifierToItem objectForKey: itemIdentifier];
1232 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1234   /* return entire set.. */
1235   return activeIdentifiers;
1238 /* for configuration palette (not yet supported) */
1239 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1241   /* return entire set... */
1242   return [identifierToItem allKeys];
1245 /* optional and unneeded */
1246 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1247 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1248 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1250 @end  /* EmacsToolbar */
1254 /* ==========================================================================
1256     Tooltip: class implementation
1258    ========================================================================== */
1260 /* Needed because NeXTstep does not provide enough control over tooltip
1261    display. */
1262 @implementation EmacsTooltip
1264 - init
1266   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1267                                             blue: 0.792 alpha: 0.95];
1268   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1269   NSFont *sfont = [font screenFont];
1270   int height = [sfont ascender] - [sfont descender];
1271 /*[font boundingRectForFont].size.height; */
1272   NSRect r = NSMakeRect (0, 0, 100, height+6);
1274   textField = [[NSTextField alloc] initWithFrame: r];
1275   [textField setFont: font];
1276   [textField setBackgroundColor: col];
1278   [textField setEditable: NO];
1279   [textField setSelectable: NO];
1280   [textField setBordered: NO];
1281   [textField setBezeled: NO];
1282   [textField setDrawsBackground: YES];
1284   win = [[NSWindow alloc]
1285             initWithContentRect: [textField frame]
1286                       styleMask: 0
1287                         backing: NSBackingStoreBuffered
1288                           defer: YES];
1289   [win setHasShadow: YES];
1290   [win setReleasedWhenClosed: NO];
1291   [win setDelegate: self];
1292   [[win contentView] addSubview: textField];
1293 /*  [win setBackgroundColor: col]; */
1294   [win setOpaque: NO];
1296   return self;
1299 - (void) dealloc
1301   [win close];
1302   [win release];
1303   [textField release];
1304   [super dealloc];
1307 - (void) setText: (char *)text
1309   NSString *str = [NSString stringWithUTF8String: text];
1310   NSRect r  = [textField frame];
1311   NSSize tooltipDims;
1313   [textField setStringValue: str];
1314   tooltipDims = [[textField cell] cellSize];
1316   r.size.width = tooltipDims.width;
1317   r.size.height = tooltipDims.height;
1318   [textField setFrame: r];
1321 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1323   NSRect wr = [win frame];
1325   wr.origin = NSMakePoint (x, y);
1326   wr.size = [textField frame].size;
1328   [win setFrame: wr display: YES];
1329   [win setLevel: NSPopUpMenuWindowLevel];
1330   [win orderFront: self];
1331   [win display];
1332   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1333                                          selector: @selector (hide)
1334                                          userInfo: nil repeats: NO];
1335   [timer retain];
1338 - (void) hide
1340   [win close];
1341   if (timer != nil)
1342     {
1343       if ([timer isValid])
1344         [timer invalidate];
1345       [timer release];
1346       timer = nil;
1347     }
1350 - (BOOL) isActive
1352   return timer != nil;
1355 - (NSRect) frame
1357   return [textField frame];
1360 @end  /* EmacsTooltip */
1364 /* ==========================================================================
1366     Popup Dialog: implementing functions
1368    ========================================================================== */
1370 struct Popdown_data
1372   NSAutoreleasePool *pool;
1373   EmacsDialogPanel *dialog;
1376 static Lisp_Object
1377 pop_down_menu (Lisp_Object arg)
1379   struct Popdown_data *unwind_data = XSAVE_POINTER (arg, 0);
1381   block_input ();
1382   if (popup_activated_flag)
1383     {
1384       EmacsDialogPanel *panel = unwind_data->dialog;
1385       popup_activated_flag = 0;
1386       [panel close];
1387       [unwind_data->pool release];
1388       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1389     }
1391   xfree (unwind_data);
1392   unblock_input ();
1394   return Qnil;
1398 Lisp_Object
1399 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1401   id dialog;
1402   Lisp_Object window, tem, title;
1403   struct frame *f;
1404   NSPoint p;
1405   BOOL isQ;
1406   NSAutoreleasePool *pool;
1408   NSTRACE (x-popup-dialog);
1410   isQ = NILP (header);
1412   if (EQ (position, Qt)
1413       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1414                                || EQ (XCAR (position), Qtool_bar))))
1415     {
1416       window = selected_window;
1417     }
1418   else if (CONSP (position))
1419     {
1420       Lisp_Object tem;
1421       tem = Fcar (position);
1422       if (XTYPE (tem) == Lisp_Cons)
1423         window = Fcar (Fcdr (position));
1424       else
1425         {
1426           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1427           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1428         }
1429     }
1430   else if (WINDOWP (position) || FRAMEP (position))
1431     {
1432       window = position;
1433     }
1434   else
1435     window = Qnil;
1437   if (FRAMEP (window))
1438     f = XFRAME (window);
1439   else if (WINDOWP (window))
1440     {
1441       CHECK_LIVE_WINDOW (window);
1442       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1443     }
1444   else
1445     CHECK_WINDOW (window);
1447   check_window_system (f);
1449   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1450   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1452   title = Fcar (contents);
1453   CHECK_STRING (title);
1455   if (NILP (Fcar (Fcdr (contents))))
1456     /* No buttons specified, add an "Ok" button so users can pop down
1457        the dialog.  */
1458     contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1460   block_input ();
1461   pool = [[NSAutoreleasePool alloc] init];
1462   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1463                                            isQuestion: isQ];
1465   {
1466     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1467     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1469     unwind_data->pool = pool;
1470     unwind_data->dialog = dialog;
1472     record_unwind_protect (pop_down_menu, make_save_pointer (unwind_data));
1473     popup_activated_flag = 1;
1474     tem = [dialog runDialogAt: p];
1475     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1476   }
1478   unblock_input ();
1480   return tem;
1484 /* ==========================================================================
1486     Popup Dialog: class implementation
1488    ========================================================================== */
1490 @interface FlippedView : NSView
1493 @end
1495 @implementation FlippedView
1496 - (BOOL)isFlipped
1498   return YES;
1500 @end
1502 @implementation EmacsDialogPanel
1504 #define SPACER          8.0
1505 #define ICONSIZE        64.0
1506 #define TEXTHEIGHT      20.0
1507 #define MINCELLWIDTH    90.0
1509 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1510               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1512   NSSize spacing = {SPACER, SPACER};
1513   NSRect area;
1514   id cell;
1515   NSImageView *imgView;
1516   FlippedView *contentView;
1517   NSImage *img;
1519   dialog_return   = Qundefined;
1520   button_values   = NULL;
1521   area.origin.x   = 3*SPACER;
1522   area.origin.y   = 2*SPACER;
1523   area.size.width = ICONSIZE;
1524   area.size.height= ICONSIZE;
1525   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1526   [img setScalesWhenResized: YES];
1527   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1528   imgView = [[NSImageView alloc] initWithFrame: area];
1529   [imgView setImage: img];
1530   [imgView setEditable: NO];
1531   [img autorelease];
1532   [imgView autorelease];
1534   aStyle = NSTitledWindowMask;
1535   flag = YES;
1536   rows = 0;
1537   cols = 1;
1538   [super initWithContentRect: contentRect styleMask: aStyle
1539                      backing: backingType defer: flag];
1540   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1541   [contentView autorelease];
1543   [self setContentView: contentView];
1545   [[self contentView] setAutoresizesSubviews: YES];
1547   [[self contentView] addSubview: imgView];
1548   [self setTitle: @""];
1550   area.origin.x   += ICONSIZE+2*SPACER;
1551 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1552   area.size.width = 400;
1553   area.size.height= TEXTHEIGHT;
1554   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1555   [[self contentView] addSubview: command];
1556   [command setStringValue: ns_app_name];
1557   [command setDrawsBackground: NO];
1558   [command setBezeled: NO];
1559   [command setSelectable: NO];
1560   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1562 /*  area.origin.x   = ICONSIZE+2*SPACER;
1563   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1564   area.size.width = 400;
1565   area.size.height= 2;
1566   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1567   [[self contentView] addSubview: tem];
1568   [tem setTitlePosition: NSNoTitle];
1569   [tem setAutoresizingMask: NSViewWidthSizable];*/
1571 /*  area.origin.x = ICONSIZE+2*SPACER; */
1572   area.origin.y += TEXTHEIGHT+SPACER;
1573   area.size.width = 400;
1574   area.size.height= TEXTHEIGHT;
1575   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1576   [[self contentView] addSubview: title];
1577   [title setDrawsBackground: NO];
1578   [title setBezeled: NO];
1579   [title setSelectable: NO];
1580   [title setFont: [NSFont systemFontOfSize: 11.0]];
1582   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1583   [cell setBordered: NO];
1584   [cell setEnabled: NO];
1585   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1586   [cell setBezelStyle: NSRoundedBezelStyle];
1588   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1589                                       mode: NSHighlightModeMatrix
1590                                  prototype: cell
1591                               numberOfRows: 0
1592                            numberOfColumns: 1];
1593   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1594                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1595   [matrix setIntercellSpacing: spacing];
1596   [matrix autorelease];
1598   [[self contentView] addSubview: matrix];
1599   [self setOneShot: YES];
1600   [self setReleasedWhenClosed: YES];
1601   [self setHidesOnDeactivate: YES];
1602   [self setStyleMask:
1603           NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask];
1605   return self;
1609 - (BOOL)windowShouldClose: (id)sender
1611   window_closed = YES;
1612   [NSApp stop:self];
1613   return NO;
1616 - (void)dealloc
1618   xfree (button_values);
1619   [super dealloc];
1622 - (void)process_dialog: (Lisp_Object) list
1624   Lisp_Object item, lst = list;
1625   int row = 0;
1626   int buttons = 0, btnnr = 0;
1628   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1629     {
1630       item = XCAR (list);
1631       if (XTYPE (item) == Lisp_Cons)
1632         ++buttons;
1633     }
1635   if (buttons > 0)
1636     button_values = (Lisp_Object *) xmalloc (buttons * sizeof (*button_values));
1638   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1639     {
1640       item = XCAR (list);
1641       if (XTYPE (item) == Lisp_String)
1642         {
1643           [self addString: SSDATA (item) row: row++];
1644         }
1645       else if (XTYPE (item) == Lisp_Cons)
1646         {
1647           button_values[btnnr] = XCDR (item);
1648           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1649           ++btnnr;
1650         }
1651       else if (NILP (item))
1652         {
1653           [self addSplit];
1654           row = 0;
1655         }
1656     }
1660 - (void)addButton: (char *)str value: (int)tag row: (int)row
1662   id cell;
1664   if (row >= rows)
1665     {
1666       [matrix addRow];
1667       rows++;
1668     }
1669   cell = [matrix cellAtRow: row column: cols-1];
1670   [cell setTarget: self];
1671   [cell setAction: @selector (clicked: )];
1672   [cell setTitle: [NSString stringWithUTF8String: str]];
1673   [cell setTag: tag];
1674   [cell setBordered: YES];
1675   [cell setEnabled: YES];
1679 - (void)addString: (char *)str row: (int)row
1681   id cell;
1683   if (row >= rows)
1684     {
1685       [matrix addRow];
1686       rows++;
1687     }
1688   cell = [matrix cellAtRow: row column: cols-1];
1689   [cell setTitle: [NSString stringWithUTF8String: str]];
1690   [cell setBordered: YES];
1691   [cell setEnabled: NO];
1695 - (void)addSplit
1697   [matrix addColumn];
1698   cols++;
1702 - (void)clicked: sender
1704   NSArray *sellist = nil;
1705   EMACS_INT seltag;
1707   sellist = [sender selectedCells];
1708   if ([sellist count] < 1)
1709     return;
1711   seltag = [[sellist objectAtIndex: 0] tag];
1712   dialog_return = button_values[seltag];
1713   [NSApp stop:self];
1717 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1719   Lisp_Object head;
1720   [super init];
1722   if (XTYPE (contents) == Lisp_Cons)
1723     {
1724       head = Fcar (contents);
1725       [self process_dialog: Fcdr (contents)];
1726     }
1727   else
1728     head = contents;
1730   if (XTYPE (head) == Lisp_String)
1731       [title setStringValue:
1732                  [NSString stringWithUTF8String: SSDATA (head)]];
1733   else if (isQ == YES)
1734       [title setStringValue: @"Question"];
1735   else
1736       [title setStringValue: @"Information"];
1738   {
1739     int i;
1740     NSRect r, s, t;
1742     if (cols == 1 && rows > 1)  /* Never told where to split */
1743       {
1744         [matrix addColumn];
1745         for (i = 0; i < rows/2; i++)
1746           {
1747             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1748                       atRow: i column: 1];
1749             [matrix removeRow: (rows+1)/2];
1750           }
1751       }
1753     [matrix sizeToFit];
1754     {
1755       NSSize csize = [matrix cellSize];
1756       if (csize.width < MINCELLWIDTH)
1757         {
1758           csize.width = MINCELLWIDTH;
1759           [matrix setCellSize: csize];
1760           [matrix sizeToCells];
1761         }
1762     }
1764     [title sizeToFit];
1765     [command sizeToFit];
1767     t = [matrix frame];
1768     r = [title frame];
1769     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1770       {
1771         t.origin.x   = r.origin.x;
1772         t.size.width = r.size.width;
1773       }
1774     r = [command frame];
1775     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1776       {
1777         t.origin.x   = r.origin.x;
1778         t.size.width = r.size.width;
1779       }
1781     r = [self frame];
1782     s = [(NSView *)[self contentView] frame];
1783     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1784     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1785     [self setFrame: r display: NO];
1786   }
1788   return self;
1793 - (void)timeout_handler: (NSTimer *)timedEntry
1795   NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1796                             location: NSMakePoint (0, 0)
1797                        modifierFlags: 0
1798                            timestamp: 0
1799                         windowNumber: [[NSApp mainWindow] windowNumber]
1800                              context: [NSApp context]
1801                              subtype: 0
1802                                data1: 0
1803                                data2: 0];
1805   timer_fired = YES;
1806   /* We use sto because stopModal/abortModal out of the main loop does not
1807      seem to work in 10.6.  But as we use stop we must send a real event so
1808      the stop is seen and acted upon.  */
1809   [NSApp stop:self];
1810   [NSApp postEvent: nxev atStart: NO];
1813 - (Lisp_Object)runDialogAt: (NSPoint)p
1815   Lisp_Object ret = Qundefined;
1817   while (popup_activated_flag)
1818     {
1819       NSTimer *tmo = nil;
1820       EMACS_TIME next_time = timer_check ();
1822       if (EMACS_TIME_VALID_P (next_time))
1823         {
1824           double time = EMACS_TIME_TO_DOUBLE (next_time);
1825           tmo = [NSTimer timerWithTimeInterval: time
1826                                         target: self
1827                                       selector: @selector (timeout_handler:)
1828                                       userInfo: 0
1829                                        repeats: NO];
1830           [[NSRunLoop currentRunLoop] addTimer: tmo
1831                                        forMode: NSModalPanelRunLoopMode];
1832         }
1833       timer_fired = NO;
1834       dialog_return = Qundefined;
1835       [NSApp runModalForWindow: self];
1836       ret = dialog_return;
1837       if (! timer_fired)
1838         {
1839           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1840           break;
1841         }
1842     }
1844   if (EQ (ret, Qundefined) && window_closed)
1845     /* Make close button pressed equivalent to C-g.  */
1846     Fsignal (Qquit, Qnil);
1848   return ret;
1851 @end
1854 /* ==========================================================================
1856     Lisp definitions
1858    ========================================================================== */
1860 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1861        doc: /* Cause the NS menu to be re-calculated.  */)
1862      (void)
1864   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1865   return Qnil;
1869 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1870        doc: /* Pop up a dialog box and return user's selection.
1871 POSITION specifies which frame to use.
1872 This is normally a mouse button event or a window or frame.
1873 If POSITION is t, it means to use the frame the mouse is on.
1874 The dialog box appears in the middle of the specified frame.
1876 CONTENTS specifies the alternatives to display in the dialog box.
1877 It is a list of the form (DIALOG ITEM1 ITEM2...).
1878 Each ITEM is a cons cell (STRING . VALUE).
1879 The return value is VALUE from the chosen item.
1881 An ITEM may also be just a string--that makes a nonselectable item.
1882 An ITEM may also be nil--that means to put all preceding items
1883 on the left of the dialog box and all following items on the right.
1884 \(By default, approximately half appear on each side.)
1886 If HEADER is non-nil, the frame title for the box is "Information",
1887 otherwise it is "Question".
1889 If the user gets rid of the dialog box without making a valid choice,
1890 for instance using the window manager, then this produces a quit and
1891 `x-popup-dialog' does not return.  */)
1892      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1894   return ns_popup_dialog (position, contents, header);
1897 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1898        doc: /* Return t if a menu or popup dialog is active.  */)
1899      (void)
1901   return popup_activated () ? Qt : Qnil;
1904 /* ==========================================================================
1906     Lisp interface declaration
1908    ========================================================================== */
1910 void
1911 syms_of_nsmenu (void)
1913 #ifndef NS_IMPL_COCOA
1914   /* Don't know how to keep track of this in Next/Open/Gnustep.  Always
1915      update menus there.  */
1916   trackingMenu = 1;
1917 #endif
1918   defsubr (&Sx_popup_dialog);
1919   defsubr (&Sns_reset_menu);
1920   defsubr (&Smenu_or_popup_active_p);
1922   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1923   staticpro (&Qdebug_on_next_call);