Make pcomplete less eager to add an extra space.
[emacs.git] / src / nsmenu.m
blob907d3eac6228a6fd83b84be7b92ec03daa32a6de
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007-2012 Free Software Foundation, Inc.
4 This file is part of GNU Emacs.
6 GNU Emacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 GNU Emacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
20 By Adrian Robert, based on code from original nsmenu.m (Carl Edman,
21 Christian Limpach, Scott Bender, Christophe de Dinechin) and code in the
22 Carbon version by Yamamoto Mitsuharu. */
24 /* This should be the first include, as it may set up #defines affecting
25    interpretation of even the system includes. */
26 #include <config.h>
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 = 0, 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 = 1, submenu = nil: Recompute all submenus.
121     3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
122    -------------------------------------------------------------------------- */
123 void
124 ns_update_menubar (struct frame *f, int 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   extern int waiting_for_input;
132   int owfi;
133   Lisp_Object items;
134   widget_value *wv, *first_wv, *prev_wv = 0;
135   int i;
137 #if NSMENUPROFILE
138   struct timeb tb;
139   long t;
140 #endif
142   NSTRACE (set_frame_menubar);
144   if (f != SELECTED_FRAME ())
145       return;
146   XSETFRAME (Vmenu_updating_frame, f);
147 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
149   BLOCK_INPUT;
150   pool = [[NSAutoreleasePool alloc] init];
152   /* Menu may have been created automatically; if so, discard it. */
153   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
154     {
155       [menu release];
156       menu = nil;
157     }
159   if (menu == nil)
160     {
161       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
162       needsSet = YES;
163     }
164   else
165     {  /* close up anything on there */
166       id attMenu = [menu attachedMenu];
167       if (attMenu != nil)
168         [attMenu close];
169     }
171 #if NSMENUPROFILE
172   ftime (&tb);
173   t = -(1000*tb.time+tb.millitm);
174 #endif
176 #ifdef NS_IMPL_GNUSTEP
177   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
178 #endif
180   if (deep_p)
181     {
182       /* Fully parse one or more of the submenus. */
183       int n = 0;
184       int *submenu_start, *submenu_end;
185       int *submenu_top_level_items, *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, int first_time, int 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 #else
726 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_2
727   if ([self supermenu] == nil)
728     [self sizeToFit];
729 #endif
730 #endif
734 /* adds an empty submenu and returns it */
735 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
737   NSString *titleStr = [NSString stringWithUTF8String: title];
738   NSMenuItem *item = [self addItemWithTitle: titleStr
739                                      action: nil /*@selector (menuDown:) */
740                               keyEquivalent: @""];
741   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
742   [self setSubmenu: submenu forItem: item];
743   [submenu release];
744   return submenu;
747 /* run a menu in popup mode */
748 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
749                  keymaps: (int)keymaps
751   EmacsView *view = FRAME_NS_VIEW (f);
752   NSEvent *e, *event;
753   long retVal;
755 /*   p = [view convertPoint:p fromView: nil]; */
756   p.y = NSHeight ([view frame]) - p.y;
757   e = [[view window] currentEvent];
758    event = [NSEvent mouseEventWithType: NSRightMouseDown
759                               location: p
760                          modifierFlags: 0
761                              timestamp: [e timestamp]
762                           windowNumber: [[view window] windowNumber]
763                                context: [e context]
764                            eventNumber: 0/*[e eventNumber] */
765                             clickCount: 1
766                               pressure: 0];
768   context_menu_value = -1;
769   [NSMenu popUpContextMenu: self withEvent: event forView: view];
770   retVal = context_menu_value;
771   context_menu_value = 0;
772   return retVal > 0
773       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
774       : Qnil;
777 @end  /* EmacsMenu */
781 /* ==========================================================================
783     Context Menu: implementing functions
785    ========================================================================== */
787 Lisp_Object
788 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
789               Lisp_Object title, const char **error)
791   EmacsMenu *pmenu;
792   NSPoint p;
793   Lisp_Object tem;
794   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
795   widget_value *wv, *first_wv = 0;
797   p.x = x; p.y = y;
799   /* now parse stage 2 as in ns_update_menubar */
800   wv = xmalloc_widget_value ();
801   wv->name = "contextmenu";
802   wv->value = 0;
803   wv->enabled = 1;
804   wv->button_type = BUTTON_TYPE_NONE;
805   wv->help = Qnil;
806   first_wv = wv;
808 #if 0
809   /* FIXME: a couple of one-line differences prevent reuse */
810   wv = digest_single_submenu (0, menu_items_used, Qnil);
811 #else
812   {
813   widget_value *save_wv = 0, *prev_wv = 0;
814   widget_value **submenu_stack
815     = alloca (menu_items_used * sizeof *submenu_stack);
816 /*   Lisp_Object *subprefix_stack
817        = alloca (menu_items_used * sizeof *subprefix_stack); */
818   int submenu_depth = 0;
819   int first_pane = 1;
820   int i;
822   /* Loop over all panes and items, filling in the tree.  */
823   i = 0;
824   while (i < menu_items_used)
825     {
826       if (EQ (AREF (menu_items, i), Qnil))
827         {
828           submenu_stack[submenu_depth++] = save_wv;
829           save_wv = prev_wv;
830           prev_wv = 0;
831           first_pane = 1;
832           i++;
833         }
834       else if (EQ (AREF (menu_items, i), Qlambda))
835         {
836           prev_wv = save_wv;
837           save_wv = submenu_stack[--submenu_depth];
838           first_pane = 0;
839           i++;
840         }
841       else if (EQ (AREF (menu_items, i), Qt)
842                && submenu_depth != 0)
843         i += MENU_ITEMS_PANE_LENGTH;
844       /* Ignore a nil in the item list.
845          It's meaningful only for dialog boxes.  */
846       else if (EQ (AREF (menu_items, i), Qquote))
847         i += 1;
848       else if (EQ (AREF (menu_items, i), Qt))
849         {
850           /* Create a new pane.  */
851           Lisp_Object pane_name, prefix;
852           const char *pane_string;
854           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
855           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
857 #ifndef HAVE_MULTILINGUAL_MENU
858           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
859             {
860               pane_name = ENCODE_MENU_STRING (pane_name);
861               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
862             }
863 #endif
864           pane_string = (NILP (pane_name)
865                          ? "" : SSDATA (pane_name));
866           /* If there is just one top-level pane, put all its items directly
867              under the top-level menu.  */
868           if (menu_items_n_panes == 1)
869             pane_string = "";
871           /* If the pane has a meaningful name,
872              make the pane a top-level menu item
873              with its items as a submenu beneath it.  */
874           if (!keymaps && strcmp (pane_string, ""))
875             {
876               wv = xmalloc_widget_value ();
877               if (save_wv)
878                 save_wv->next = wv;
879               else
880                 first_wv->contents = wv;
881               wv->name = pane_string;
882               if (keymaps && !NILP (prefix))
883                 wv->name++;
884               wv->value = 0;
885               wv->enabled = 1;
886               wv->button_type = BUTTON_TYPE_NONE;
887               wv->help = Qnil;
888               save_wv = wv;
889               prev_wv = 0;
890             }
891           else if (first_pane)
892             {
893               save_wv = wv;
894               prev_wv = 0;
895             }
896           first_pane = 0;
897           i += MENU_ITEMS_PANE_LENGTH;
898         }
899       else
900         {
901           /* Create a new item within current pane.  */
902           Lisp_Object item_name, enable, descrip, def, type, selected, help;
903           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
904           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
905           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
906           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
907           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
908           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
909           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
911 #ifndef HAVE_MULTILINGUAL_MENU
912           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
913             {
914               item_name = ENCODE_MENU_STRING (item_name);
915               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
916             }
918           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
919             {
920               descrip = ENCODE_MENU_STRING (descrip);
921               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
922             }
923 #endif /* not HAVE_MULTILINGUAL_MENU */
925           wv = xmalloc_widget_value ();
926           if (prev_wv)
927             prev_wv->next = wv;
928           else
929             save_wv->contents = wv;
930           wv->name = SSDATA (item_name);
931           if (!NILP (descrip))
932             wv->key = SSDATA (descrip);
933           wv->value = 0;
934           /* If this item has a null value,
935              make the call_data null so that it won't display a box
936              when the mouse is on it.  */
937           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
938           wv->enabled = !NILP (enable);
940           if (NILP (type))
941             wv->button_type = BUTTON_TYPE_NONE;
942           else if (EQ (type, QCtoggle))
943             wv->button_type = BUTTON_TYPE_TOGGLE;
944           else if (EQ (type, QCradio))
945             wv->button_type = BUTTON_TYPE_RADIO;
946           else
947             emacs_abort ();
949           wv->selected = !NILP (selected);
951           if (! STRINGP (help))
952             help = Qnil;
954           wv->help = help;
956           prev_wv = wv;
958           i += MENU_ITEMS_ITEM_LENGTH;
959         }
960     }
961   }
962 #endif
964   if (!NILP (title))
965     {
966       widget_value *wv_title = xmalloc_widget_value ();
967       widget_value *wv_sep = xmalloc_widget_value ();
969       /* Maybe replace this separator with a bitmap or owner-draw item
970          so that it looks better.  Having two separators looks odd.  */
971       wv_sep->name = "--";
972       wv_sep->next = first_wv->contents;
973       wv_sep->help = Qnil;
975 #ifndef HAVE_MULTILINGUAL_MENU
976       if (STRING_MULTIBYTE (title))
977         title = ENCODE_MENU_STRING (title);
978 #endif
980       wv_title->name = SSDATA (title);
981       wv_title->enabled = NO;
982       wv_title->button_type = BUTTON_TYPE_NONE;
983       wv_title->help = Qnil;
984       wv_title->next = wv_sep;
985       first_wv->contents = wv_title;
986     }
988   pmenu = [[EmacsMenu alloc] initWithTitle:
989                                [NSString stringWithUTF8String: SSDATA (title)]];
990   [pmenu fillWithWidgetValue: first_wv->contents];
991   free_menubar_widget_value_tree (first_wv);
992   unbind_to (specpdl_count, Qnil);
994   popup_activated_flag = 1;
995   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
996   popup_activated_flag = 0;
997   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
999   return tem;
1003 /* ==========================================================================
1005     Toolbar: externally-called functions
1007    ========================================================================== */
1009 void
1010 free_frame_tool_bar (FRAME_PTR f)
1011 /* --------------------------------------------------------------------------
1012     Under NS we just hide the toolbar until it might be needed again.
1013    -------------------------------------------------------------------------- */
1015   BLOCK_INPUT;
1016   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1017   FRAME_TOOLBAR_HEIGHT (f) = 0;
1018   UNBLOCK_INPUT;
1021 void
1022 update_frame_tool_bar (FRAME_PTR f)
1023 /* --------------------------------------------------------------------------
1024     Update toolbar contents
1025    -------------------------------------------------------------------------- */
1027   int i;
1028   EmacsView *view = FRAME_NS_VIEW (f);
1029   NSWindow *window = [view window];
1030   EmacsToolbar *toolbar = [view toolbar];
1032   BLOCK_INPUT;
1033   [toolbar clearActive];
1035   /* update EmacsToolbar as in GtkUtils, build items list */
1036   for (i = 0; i < f->n_tool_bar_items; ++i)
1037     {
1038 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1039                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1041       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1042       int idx;
1043       ptrdiff_t img_id;
1044       struct image *img;
1045       Lisp_Object image;
1046       Lisp_Object helpObj;
1047       const char *helpText;
1049       /* If image is a vector, choose the image according to the
1050          button state.  */
1051       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1052       if (VECTORP (image))
1053         {
1054           /* NS toolbar auto-computes disabled and selected images */
1055           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1056           eassert (ASIZE (image) >= idx);
1057           image = AREF (image, idx);
1058         }
1059       else
1060         {
1061           idx = -1;
1062         }
1063       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1064       if (NILP (helpObj))
1065         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1066       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1068       /* Ignore invalid image specifications.  */
1069       if (!valid_image_p (image))
1070         {
1071           /* Don't log anything, GNUS makes invalid images all the time.  */
1072           continue;
1073         }
1075       img_id = lookup_image (f, image);
1076       img = IMAGE_FROM_ID (f, img_id);
1077       prepare_image_for_display (f, img);
1079       if (img->load_failed_p || img->pixmap == nil)
1080         {
1081           NSLog (@"Could not prepare toolbar image for display.");
1082           continue;
1083         }
1085       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1086                                enabled: enabled_p];
1087 #undef TOOLPROP
1088     }
1090   if (![toolbar isVisible])
1091       [toolbar setVisible: YES];
1093   if ([toolbar changed])
1094     {
1095       /* inform app that toolbar has changed */
1096       NSDictionary *dict = [toolbar configurationDictionary];
1097       NSMutableDictionary *newDict = [dict mutableCopy];
1098       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1099       id key;
1100       while ((key = [keys nextObject]) != nil)
1101         {
1102           NSObject *val = [dict objectForKey: key];
1103           if ([val isKindOfClass: [NSArray class]])
1104             {
1105               [newDict setObject:
1106                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1107                           forKey: key];
1108               break;
1109             }
1110         }
1111       [toolbar setConfigurationFromDictionary: newDict];
1112       [newDict release];
1113     }
1115   FRAME_TOOLBAR_HEIGHT (f) =
1116     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1117     - FRAME_NS_TITLEBAR_HEIGHT (f);
1118   UNBLOCK_INPUT;
1122 /* ==========================================================================
1124     Toolbar: class implementation
1126    ========================================================================== */
1128 @implementation EmacsToolbar
1130 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1132   self = [super initWithIdentifier: identifier];
1133   emacsView = view;
1134   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1135   [self setSizeMode: NSToolbarSizeModeSmall];
1136   [self setDelegate: self];
1137   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1138   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1139   prevEnablement = enablement = 0L;
1140   return self;
1143 - (void)dealloc
1145   [prevIdentifiers release];
1146   [activeIdentifiers release];
1147   [identifierToItem release];
1148   [super dealloc];
1151 - (void) clearActive
1153   [prevIdentifiers release];
1154   prevIdentifiers = [activeIdentifiers copy];
1155   [activeIdentifiers removeAllObjects];
1156   prevEnablement = enablement;
1157   enablement = 0L;
1160 - (BOOL) changed
1162   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1163     enablement == prevEnablement ? NO : YES;
1166 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1167                         helpText: (const char *)help enabled: (BOOL)enabled
1169   /* 1) come up w/identifier */
1170   NSString *identifier
1171       = [NSString stringWithFormat: @"%u", [img hash]];
1173   /* 2) create / reuse item */
1174   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1175   if (item == nil)
1176     {
1177       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1178                autorelease];
1179       [item setImage: img];
1180       [item setToolTip: [NSString stringWithUTF8String: help]];
1181       [item setTarget: emacsView];
1182       [item setAction: @selector (toolbarClicked:)];
1183     }
1185   [item setTag: idx];
1186   [item setEnabled: enabled];
1188   /* 3) update state */
1189   [identifierToItem setObject: item forKey: identifier];
1190   [activeIdentifiers addObject: identifier];
1191   enablement = (enablement << 1) | (enabled == YES);
1194 /* This overrides super's implementation, which automatically sets
1195    all items to enabled state (for some reason). */
1196 - (void)validateVisibleItems { }
1199 /* delegate methods */
1201 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1202       itemForItemIdentifier: (NSString *)itemIdentifier
1203   willBeInsertedIntoToolbar: (BOOL)flag
1205   /* look up NSToolbarItem by identifier and return... */
1206   return [identifierToItem objectForKey: itemIdentifier];
1209 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1211   /* return entire set.. */
1212   return activeIdentifiers;
1215 /* for configuration palette (not yet supported) */
1216 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1218   /* return entire set... */
1219   return [identifierToItem allKeys];
1222 /* optional and unneeded */
1223 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1224 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1225 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1227 @end  /* EmacsToolbar */
1231 /* ==========================================================================
1233     Tooltip: class implementation
1235    ========================================================================== */
1237 /* Needed because NeXTstep does not provide enough control over tooltip
1238    display. */
1239 @implementation EmacsTooltip
1241 - init
1243   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1244                                             blue: 0.792 alpha: 0.95];
1245   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1246   NSFont *sfont = [font screenFont];
1247   int height = [sfont ascender] - [sfont descender];
1248 /*[font boundingRectForFont].size.height; */
1249   NSRect r = NSMakeRect (0, 0, 100, height+6);
1251   textField = [[NSTextField alloc] initWithFrame: r];
1252   [textField setFont: font];
1253   [textField setBackgroundColor: col];
1255   [textField setEditable: NO];
1256   [textField setSelectable: NO];
1257   [textField setBordered: NO];
1258   [textField setBezeled: NO];
1259   [textField setDrawsBackground: YES];
1261   win = [[NSWindow alloc]
1262             initWithContentRect: [textField frame]
1263                       styleMask: 0
1264                         backing: NSBackingStoreBuffered
1265                           defer: YES];
1266   [win setHasShadow: YES];
1267   [win setReleasedWhenClosed: NO];
1268   [win setDelegate: self];
1269   [[win contentView] addSubview: textField];
1270 /*  [win setBackgroundColor: col]; */
1271   [win setOpaque: NO];
1273   return self;
1276 - (void) dealloc
1278   [win close];
1279   [win release];
1280   [textField release];
1281   [super dealloc];
1284 - (void) setText: (char *)text
1286   NSString *str = [NSString stringWithUTF8String: text];
1287   NSRect r  = [textField frame];
1288   NSSize tooltipDims;
1290   [textField setStringValue: str];
1291   tooltipDims = [[textField cell] cellSize];
1293   r.size.width = tooltipDims.width;
1294   r.size.height = tooltipDims.height;
1295   [textField setFrame: r];
1298 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1300   NSRect wr = [win frame];
1302   wr.origin = NSMakePoint (x, y);
1303   wr.size = [textField frame].size;
1305   [win setFrame: wr display: YES];
1306   [win orderFront: self];
1307   [win display];
1308   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1309                                          selector: @selector (hide)
1310                                          userInfo: nil repeats: NO];
1311   [timer retain];
1314 - (void) hide
1316   [win close];
1317   if (timer != nil)
1318     {
1319       if ([timer isValid])
1320         [timer invalidate];
1321       [timer release];
1322       timer = nil;
1323     }
1326 - (BOOL) isActive
1328   return timer != nil;
1331 - (NSRect) frame
1333   return [textField frame];
1336 @end  /* EmacsTooltip */
1340 /* ==========================================================================
1342     Popup Dialog: implementing functions
1344    ========================================================================== */
1346 struct Popdown_data
1348   NSAutoreleasePool *pool;
1349   EmacsDialogPanel *dialog;
1352 static Lisp_Object
1353 pop_down_menu (Lisp_Object arg)
1355   struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1356   struct Popdown_data *unwind_data = (struct Popdown_data *) p->pointer;
1358   BLOCK_INPUT;
1359   if (popup_activated_flag)
1360     {
1361       EmacsDialogPanel *panel = unwind_data->dialog;
1362       popup_activated_flag = 0;
1363       [panel close];
1364       [unwind_data->pool release];
1365       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1366     }
1368   xfree (unwind_data);
1369   UNBLOCK_INPUT;
1371   return Qnil;
1375 Lisp_Object
1376 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1378   id dialog;
1379   Lisp_Object window, tem, title;
1380   struct frame *f;
1381   NSPoint p;
1382   BOOL isQ;
1383   NSAutoreleasePool *pool;
1385   NSTRACE (x-popup-dialog);
1387   check_ns ();
1389   isQ = NILP (header);
1391   if (EQ (position, Qt)
1392       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1393                                || EQ (XCAR (position), Qtool_bar))))
1394     {
1395       window = selected_window;
1396     }
1397   else if (CONSP (position))
1398     {
1399       Lisp_Object tem;
1400       tem = Fcar (position);
1401       if (XTYPE (tem) == Lisp_Cons)
1402         window = Fcar (Fcdr (position));
1403       else
1404         {
1405           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1406           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1407         }
1408     }
1409   else if (WINDOWP (position) || FRAMEP (position))
1410     {
1411       window = position;
1412     }
1413   else
1414     window = Qnil;
1416   if (FRAMEP (window))
1417     f = XFRAME (window);
1418   else if (WINDOWP (window))
1419     {
1420       CHECK_LIVE_WINDOW (window);
1421       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1422     }
1423   else
1424     CHECK_WINDOW (window);
1426   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1427   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1429   title = Fcar (contents);
1430   CHECK_STRING (title);
1432   if (NILP (Fcar (Fcdr (contents))))
1433     /* No buttons specified, add an "Ok" button so users can pop down
1434        the dialog.  */
1435     contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1437   BLOCK_INPUT;
1438   pool = [[NSAutoreleasePool alloc] init];
1439   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1440                                            isQuestion: isQ];
1442   {
1443     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1444     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1446     unwind_data->pool = pool;
1447     unwind_data->dialog = dialog;
1449     record_unwind_protect (pop_down_menu, make_save_value (unwind_data, 0));
1450     popup_activated_flag = 1;
1451     tem = [dialog runDialogAt: p];
1452     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1453   }
1455   UNBLOCK_INPUT;
1457   return tem;
1461 /* ==========================================================================
1463     Popup Dialog: class implementation
1465    ========================================================================== */
1467 @interface FlippedView : NSView
1470 @end
1472 @implementation FlippedView
1473 - (BOOL)isFlipped
1475   return YES;
1477 @end
1479 @implementation EmacsDialogPanel
1481 #define SPACER          8.0
1482 #define ICONSIZE        64.0
1483 #define TEXTHEIGHT      20.0
1484 #define MINCELLWIDTH    90.0
1486 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1487               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1489   NSSize spacing = {SPACER, SPACER};
1490   NSRect area;
1491   id cell;
1492   NSImageView *imgView;
1493   FlippedView *contentView;
1494   NSImage *img;
1496   dialog_return   = Qundefined;
1497   button_values   = NULL;
1498   area.origin.x   = 3*SPACER;
1499   area.origin.y   = 2*SPACER;
1500   area.size.width = ICONSIZE;
1501   area.size.height= ICONSIZE;
1502   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1503   [img setScalesWhenResized: YES];
1504   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1505   imgView = [[NSImageView alloc] initWithFrame: area];
1506   [imgView setImage: img];
1507   [imgView setEditable: NO];
1508   [img autorelease];
1509   [imgView autorelease];
1511   aStyle = NSTitledWindowMask;
1512   flag = YES;
1513   rows = 0;
1514   cols = 1;
1515   [super initWithContentRect: contentRect styleMask: aStyle
1516                      backing: backingType defer: flag];
1517   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1518   [contentView autorelease];
1520   [self setContentView: contentView];
1522   [[self contentView] setAutoresizesSubviews: YES];
1524   [[self contentView] addSubview: imgView];
1525   [self setTitle: @""];
1527   area.origin.x   += ICONSIZE+2*SPACER;
1528 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1529   area.size.width = 400;
1530   area.size.height= TEXTHEIGHT;
1531   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1532   [[self contentView] addSubview: command];
1533   [command setStringValue: ns_app_name];
1534   [command setDrawsBackground: NO];
1535   [command setBezeled: NO];
1536   [command setSelectable: NO];
1537   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1539 /*  area.origin.x   = ICONSIZE+2*SPACER;
1540   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1541   area.size.width = 400;
1542   area.size.height= 2;
1543   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1544   [[self contentView] addSubview: tem];
1545   [tem setTitlePosition: NSNoTitle];
1546   [tem setAutoresizingMask: NSViewWidthSizable];*/
1548 /*  area.origin.x = ICONSIZE+2*SPACER; */
1549   area.origin.y += TEXTHEIGHT+SPACER;
1550   area.size.width = 400;
1551   area.size.height= TEXTHEIGHT;
1552   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1553   [[self contentView] addSubview: title];
1554   [title setDrawsBackground: NO];
1555   [title setBezeled: NO];
1556   [title setSelectable: NO];
1557   [title setFont: [NSFont systemFontOfSize: 11.0]];
1559   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1560   [cell setBordered: NO];
1561   [cell setEnabled: NO];
1562   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1563   [cell setBezelStyle: NSRoundedBezelStyle];
1565   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1566                                       mode: NSHighlightModeMatrix
1567                                  prototype: cell
1568                               numberOfRows: 0
1569                            numberOfColumns: 1];
1570   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1571                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1572   [matrix setIntercellSpacing: spacing];
1573   [matrix autorelease];
1575   [[self contentView] addSubview: matrix];
1576   [self setOneShot: YES];
1577   [self setReleasedWhenClosed: YES];
1578   [self setHidesOnDeactivate: YES];
1579   [self setStyleMask:
1580           NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask];
1582   return self;
1586 - (BOOL)windowShouldClose: (id)sender
1588   window_closed = YES;
1589   [NSApp stop:self];
1590   return NO;
1593 - (void)dealloc
1595   xfree (button_values);
1596   [super dealloc];
1599 - (void)process_dialog: (Lisp_Object) list
1601   Lisp_Object item, lst = list;
1602   int row = 0;
1603   int buttons = 0, btnnr = 0;
1605   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1606     {
1607       item = XCAR (list);
1608       if (XTYPE (item) == Lisp_Cons)
1609         ++buttons;
1610     }
1612   if (buttons > 0)
1613     button_values = (Lisp_Object *) xmalloc (buttons * sizeof (*button_values));
1615   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1616     {
1617       item = XCAR (list);
1618       if (XTYPE (item) == Lisp_String)
1619         {
1620           [self addString: SSDATA (item) row: row++];
1621         }
1622       else if (XTYPE (item) == Lisp_Cons)
1623         {
1624           button_values[btnnr] = XCDR (item);
1625           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1626           ++btnnr;
1627         }
1628       else if (NILP (item))
1629         {
1630           [self addSplit];
1631           row = 0;
1632         }
1633     }
1637 - (void)addButton: (char *)str value: (int)tag row: (int)row
1639   id cell;
1641   if (row >= rows)
1642     {
1643       [matrix addRow];
1644       rows++;
1645     }
1646   cell = [matrix cellAtRow: row column: cols-1];
1647   [cell setTarget: self];
1648   [cell setAction: @selector (clicked: )];
1649   [cell setTitle: [NSString stringWithUTF8String: str]];
1650   [cell setTag: tag];
1651   [cell setBordered: YES];
1652   [cell setEnabled: YES];
1656 - (void)addString: (char *)str row: (int)row
1658   id cell;
1660   if (row >= rows)
1661     {
1662       [matrix addRow];
1663       rows++;
1664     }
1665   cell = [matrix cellAtRow: row column: cols-1];
1666   [cell setTitle: [NSString stringWithUTF8String: str]];
1667   [cell setBordered: YES];
1668   [cell setEnabled: NO];
1672 - (void)addSplit
1674   [matrix addColumn];
1675   cols++;
1679 - (void)clicked: sender
1681   NSArray *sellist = nil;
1682   EMACS_INT seltag;
1684   sellist = [sender selectedCells];
1685   if ([sellist count] < 1)
1686     return;
1688   seltag = [[sellist objectAtIndex: 0] tag];
1689   dialog_return = button_values[seltag];
1690   [NSApp stop:self];
1694 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1696   Lisp_Object head;
1697   [super init];
1699   if (XTYPE (contents) == Lisp_Cons)
1700     {
1701       head = Fcar (contents);
1702       [self process_dialog: Fcdr (contents)];
1703     }
1704   else
1705     head = contents;
1707   if (XTYPE (head) == Lisp_String)
1708       [title setStringValue:
1709                  [NSString stringWithUTF8String: SSDATA (head)]];
1710   else if (isQ == YES)
1711       [title setStringValue: @"Question"];
1712   else
1713       [title setStringValue: @"Information"];
1715   {
1716     int i;
1717     NSRect r, s, t;
1719     if (cols == 1 && rows > 1)  /* Never told where to split */
1720       {
1721         [matrix addColumn];
1722         for (i = 0; i < rows/2; i++)
1723           {
1724             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1725                       atRow: i column: 1];
1726             [matrix removeRow: (rows+1)/2];
1727           }
1728       }
1730     [matrix sizeToFit];
1731     {
1732       NSSize csize = [matrix cellSize];
1733       if (csize.width < MINCELLWIDTH)
1734         {
1735           csize.width = MINCELLWIDTH;
1736           [matrix setCellSize: csize];
1737           [matrix sizeToCells];
1738         }
1739     }
1741     [title sizeToFit];
1742     [command sizeToFit];
1744     t = [matrix frame];
1745     r = [title frame];
1746     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1747       {
1748         t.origin.x   = r.origin.x;
1749         t.size.width = r.size.width;
1750       }
1751     r = [command frame];
1752     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1753       {
1754         t.origin.x   = r.origin.x;
1755         t.size.width = r.size.width;
1756       }
1758     r = [self frame];
1759     s = [(NSView *)[self contentView] frame];
1760     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1761     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1762     [self setFrame: r display: NO];
1763   }
1765   return self;
1770 - (void)timeout_handler: (NSTimer *)timedEntry
1772   NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1773                             location: NSMakePoint (0, 0)
1774                        modifierFlags: 0
1775                            timestamp: 0
1776                         windowNumber: [[NSApp mainWindow] windowNumber]
1777                              context: [NSApp context]
1778                              subtype: 0
1779                                data1: 0
1780                                data2: 0];
1782   timer_fired = YES;
1783   /* We use sto because stopModal/abortModal out of the main loop does not
1784      seem to work in 10.6.  But as we use stop we must send a real event so
1785      the stop is seen and acted upon.  */
1786   [NSApp stop:self];
1787   [NSApp postEvent: nxev atStart: NO];
1790 - (Lisp_Object)runDialogAt: (NSPoint)p
1792   Lisp_Object ret = Qundefined;
1794   while (popup_activated_flag)
1795     {
1796       NSTimer *tmo = nil;
1797       EMACS_TIME next_time = timer_check ();
1799       if (EMACS_TIME_VALID_P (next_time))
1800         {
1801           double time = EMACS_TIME_TO_DOUBLE (next_time);
1802           tmo = [NSTimer timerWithTimeInterval: time
1803                                         target: self
1804                                       selector: @selector (timeout_handler:)
1805                                       userInfo: 0
1806                                        repeats: NO];
1807           [[NSRunLoop currentRunLoop] addTimer: tmo
1808                                        forMode: NSModalPanelRunLoopMode];
1809         }
1810       timer_fired = NO;
1811       dialog_return = Qundefined;
1812       [NSApp runModalForWindow: self];
1813       ret = dialog_return;
1814       if (! timer_fired)
1815         {
1816           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1817           break;
1818         }
1819     }
1821   if (EQ (ret, Qundefined) && window_closed)
1822     /* Make close button pressed equivalent to C-g.  */
1823     Fsignal (Qquit, Qnil);
1825   return ret;
1828 @end
1831 /* ==========================================================================
1833     Lisp definitions
1835    ========================================================================== */
1837 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1838        doc: /* Cause the NS menu to be re-calculated.  */)
1839      (void)
1841   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1842   return Qnil;
1846 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1847        doc: /* Pop up a dialog box and return user's selection.
1848 POSITION specifies which frame to use.
1849 This is normally a mouse button event or a window or frame.
1850 If POSITION is t, it means to use the frame the mouse is on.
1851 The dialog box appears in the middle of the specified frame.
1853 CONTENTS specifies the alternatives to display in the dialog box.
1854 It is a list of the form (DIALOG ITEM1 ITEM2...).
1855 Each ITEM is a cons cell (STRING . VALUE).
1856 The return value is VALUE from the chosen item.
1858 An ITEM may also be just a string--that makes a nonselectable item.
1859 An ITEM may also be nil--that means to put all preceding items
1860 on the left of the dialog box and all following items on the right.
1861 \(By default, approximately half appear on each side.)
1863 If HEADER is non-nil, the frame title for the box is "Information",
1864 otherwise it is "Question".
1866 If the user gets rid of the dialog box without making a valid choice,
1867 for instance using the window manager, then this produces a quit and
1868 `x-popup-dialog' does not return.  */)
1869      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1871   return ns_popup_dialog (position, contents, header);
1874 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1875        doc: /* Return t if a menu or popup dialog is active.  */)
1876      (void)
1878   return popup_activated () ? Qt : Qnil;
1881 /* ==========================================================================
1883     Lisp interface declaration
1885    ========================================================================== */
1887 void
1888 syms_of_nsmenu (void)
1890 #ifndef NS_IMPL_COCOA
1891   /* Don't know how to keep track of this in Next/Open/Gnustep.  Always
1892      update menus there.  */
1893   trackingMenu = 1;
1894 #endif
1895   defsubr (&Sx_popup_dialog);
1896   defsubr (&Sns_reset_menu);
1897   defsubr (&Smenu_or_popup_active_p);
1899   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1900   staticpro (&Qdebug_on_next_call);