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