merge trunk
[emacs.git] / src / nsmenu.m
blob648b568d180af8e7cfe26ec2f5f2ae9b8e968ae6
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007-2013 Free Software Foundation, Inc.
4 This file is part of GNU Emacs.
6 GNU Emacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 GNU Emacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
20 By Adrian Robert, based on code from original nsmenu.m (Carl Edman,
21 Christian Limpach, Scott Bender, Christophe de Dinechin) and code in the
22 Carbon version by Yamamoto Mitsuharu. */
24 /* This should be the first include, as it may set up #defines affecting
25    interpretation of even the system includes. */
26 #include <config.h>
28 #include "lisp.h"
29 #include "window.h"
30 #include "character.h"
31 #include "buffer.h"
32 #include "keymap.h"
33 #include "coding.h"
34 #include "commands.h"
35 #include "blockinput.h"
36 #include "nsterm.h"
37 #include "termhooks.h"
38 #include "keyboard.h"
39 #include "menu.h"
41 #define NSMENUPROFILE 0
43 #if NSMENUPROFILE
44 #include <sys/timeb.h>
45 #include <sys/types.h>
46 #endif
48 #define MenuStagger 10.0
50 #if 0
51 int menu_trace_num = 0;
52 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
53                                 __FILE__, __LINE__, ++menu_trace_num)
54 #else
55 #define NSTRACE(x)
56 #endif
58 #if 0
59 /* Include lisp -> C common menu parsing code */
60 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
61 #include "nsmenu_common.c"
62 #endif
64 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
65 extern Lisp_Object QCtoggle, QCradio;
67 Lisp_Object Qdebug_on_next_call;
68 extern Lisp_Object Qoverriding_local_map, Qoverriding_terminal_local_map;
70 extern long context_menu_value;
71 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
73 /* Nonzero means a menu is currently active.  */
74 static int popup_activated_flag;
76 /* Nonzero means we are tracking and updating menus.  */
77 static int trackingMenu;
80 /* NOTE: toolbar implementation is at end,
81   following complete menu implementation. */
84 /* ==========================================================================
86     Menu: Externally-called functions
88    ========================================================================== */
91 /* Supposed to discard menubar and free storage.  Since we share the
92    menubar among frames and update its context for the focused window,
93    there is nothing to do here. */
94 void
95 free_frame_menubar (struct frame *f)
97   return;
102 popup_activated (void)
104   return popup_activated_flag;
108 /* --------------------------------------------------------------------------
109     Update menubar.  Three cases:
110     1) ! deep_p, submenu = nil: Fresh switch onto a frame -- either set up
111        just top-level menu strings (OS X), or goto case (2) (GNUstep).
112     2) deep_p, submenu = nil: Recompute all submenus.
113     3) deep_p, submenu = non-nil: Update contents of a single submenu.
114    -------------------------------------------------------------------------- */
115 void
116 ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu)
118   NSAutoreleasePool *pool;
119   id menu = [NSApp mainMenu];
120   static EmacsMenu *last_submenu = nil;
121   BOOL needsSet = NO;
122   const char *submenuTitle = [[submenu title] UTF8String];
123   bool owfi;
124   Lisp_Object items;
125   widget_value *wv, *first_wv, *prev_wv = 0;
126   int i;
128 #if NSMENUPROFILE
129   struct timeb tb;
130   long t;
131 #endif
133   NSTRACE (ns_update_menubar);
135   if (f != SELECTED_FRAME ())
136       return;
137   XSETFRAME (Vmenu_updating_frame, f);
138 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
140   block_input ();
141   pool = [[NSAutoreleasePool alloc] init];
143   /* Menu may have been created automatically; if so, discard it. */
144   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
145     {
146       [menu release];
147       menu = nil;
148     }
150   if (menu == nil)
151     {
152       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
153       needsSet = YES;
154     }
155   else
156     {  /* close up anything on there */
157       id attMenu = [menu attachedMenu];
158       if (attMenu != nil)
159         [attMenu close];
160     }
162 #if NSMENUPROFILE
163   ftime (&tb);
164   t = -(1000*tb.time+tb.millitm);
165 #endif
167 #ifdef NS_IMPL_GNUSTEP
168   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
169 #endif
171   if (deep_p)
172     {
173       /* Fully parse one or more of the submenus. */
174       int n = 0;
175       int *submenu_start, *submenu_end;
176       bool *submenu_top_level_items;
177       int *submenu_n_panes;
178       struct buffer *prev = current_buffer;
179       Lisp_Object buffer;
180       ptrdiff_t specpdl_count = SPECPDL_INDEX ();
181       int previous_menu_items_used = f->menu_bar_items_used;
182       Lisp_Object *previous_items
183         = alloca (previous_menu_items_used * sizeof *previous_items);
185       /* lisp preliminaries */
186       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->contents;
187       specbind (Qinhibit_quit, Qt);
188       specbind (Qdebug_on_next_call, Qnil);
189       record_unwind_save_match_data ();
190       if (NILP (Voverriding_local_map_menu_flag))
191         {
192           specbind (Qoverriding_terminal_local_map, Qnil);
193           specbind (Qoverriding_local_map, Qnil);
194         }
195       set_buffer_internal_1 (XBUFFER (buffer));
197       /* TODO: for some reason this is not needed in other terms,
198            but some menu updates call Info-extract-pointer which causes
199            abort-on-error if waiting-for-input.  Needs further investigation. */
200       owfi = waiting_for_input;
201       waiting_for_input = 0;
203       /* lucid hook and possible reset */
204       safe_run_hooks (Qactivate_menubar_hook);
205       if (! NILP (Vlucid_menu_bar_dirty_flag))
206         call0 (Qrecompute_lucid_menubar);
207       safe_run_hooks (Qmenu_bar_update_hook);
208       fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
210       /* Now ready to go */
211       items = FRAME_MENU_BAR_ITEMS (f);
213       /* Save the frame's previous menu bar contents data */
214       if (previous_menu_items_used)
215         memcpy (previous_items, aref_addr (f->menu_bar_vector, 0),
216                 previous_menu_items_used * sizeof (Lisp_Object));
218       /* parse stage 1: extract from lisp */
219       save_menu_items ();
221       menu_items = f->menu_bar_vector;
222       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
223       submenu_start = alloca (ASIZE (items) * sizeof *submenu_start);
224       submenu_end = alloca (ASIZE (items) * sizeof *submenu_end);
225       submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes);
226       submenu_top_level_items = alloca (ASIZE (items)
227                                         * sizeof *submenu_top_level_items);
228       init_menu_items ();
229       for (i = 0; i < ASIZE (items); i += 4)
230         {
231           Lisp_Object key, string, maps;
233           key = AREF (items, i);
234           string = AREF (items, i + 1);
235           maps = AREF (items, i + 2);
236           if (NILP (string))
237             break;
239           /* FIXME: we'd like to only parse the needed submenu, but this
240                was causing crashes in the _common parsing code.. need to make
241                sure proper initialization done.. */
242 /*        if (submenu && strcmp (submenuTitle, SSDATA (string)))
243              continue; */
245           submenu_start[i] = menu_items_used;
247           menu_items_n_panes = 0;
248           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
249           submenu_n_panes[i] = menu_items_n_panes;
250           submenu_end[i] = menu_items_used;
251           n++;
252         }
254       finish_menu_items ();
255       waiting_for_input = owfi;
258       if (submenu && n == 0)
259         {
260           /* should have found a menu for this one but didn't */
261           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
262                   submenuTitle);
263           discard_menu_items ();
264           unbind_to (specpdl_count, Qnil);
265           [pool release];
266           unblock_input ();
267           return;
268         }
270       /* parse stage 2: insert into lucid 'widget_value' structures
271          [comments in other terms say not to evaluate lisp code here] */
272       wv = xmalloc_widget_value ();
273       wv->name = "menubar";
274       wv->value = 0;
275       wv->enabled = 1;
276       wv->button_type = BUTTON_TYPE_NONE;
277       wv->help = Qnil;
278       first_wv = wv;
280       for (i = 0; i < 4*n; i += 4)
281         {
282           menu_items_n_panes = submenu_n_panes[i];
283           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
284                                       submenu_top_level_items[i]);
285           if (prev_wv)
286             prev_wv->next = wv;
287           else
288             first_wv->contents = wv;
289           /* Don't set wv->name here; GC during the loop might relocate it.  */
290           wv->enabled = 1;
291           wv->button_type = BUTTON_TYPE_NONE;
292           prev_wv = wv;
293         }
295       set_buffer_internal_1 (prev);
297       /* Compare the new menu items with previous, and leave off if no change */
298       /* FIXME: following other terms here, but seems like this should be
299            done before parse stage 2 above, since its results aren't used */
300       if (previous_menu_items_used
301           && (!submenu || (submenu && submenu == last_submenu))
302           && menu_items_used == previous_menu_items_used)
303         {
304           for (i = 0; i < previous_menu_items_used; i++)
305             /* FIXME: this ALWAYS fails on Buffers menu items.. something
306                  about their strings causes them to change every time, so we
307                  double-check failures */
308             if (!EQ (previous_items[i], AREF (menu_items, i)))
309               if (!(STRINGP (previous_items[i])
310                     && STRINGP (AREF (menu_items, i))
311                     && !strcmp (SSDATA (previous_items[i]),
312                                 SSDATA (AREF (menu_items, i)))))
313                   break;
314           if (i == previous_menu_items_used)
315             {
316               /* No change.. */
318 #if NSMENUPROFILE
319               ftime (&tb);
320               t += 1000*tb.time+tb.millitm;
321               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
322 #endif
324               free_menubar_widget_value_tree (first_wv);
325               discard_menu_items ();
326               unbind_to (specpdl_count, Qnil);
327               [pool release];
328               unblock_input ();
329               return;
330             }
331         }
332       /* The menu items are different, so store them in the frame */
333       /* FIXME: this is not correct for single-submenu case */
334       fset_menu_bar_vector (f, menu_items);
335       f->menu_bar_items_used = menu_items_used;
337       /* Calls restore_menu_items, etc., as they were outside */
338       unbind_to (specpdl_count, Qnil);
340       /* Parse stage 2a: now GC cannot happen during the lifetime of the
341          widget_value, so it's safe to store data from a Lisp_String */
342       wv = first_wv->contents;
343       for (i = 0; i < ASIZE (items); i += 4)
344         {
345           Lisp_Object string;
346           string = AREF (items, i + 1);
347           if (NILP (string))
348             break;
349 /*           if (submenu && strcmp (submenuTitle, SSDATA (string)))
350                continue; */
352           wv->name = SSDATA (string);
353           update_submenu_strings (wv->contents);
354           wv = wv->next;
355         }
357       /* Now, update the NS menu; if we have a submenu, use that, otherwise
358          create a new menu for each sub and fill it. */
359       if (submenu)
360         {
361           for (wv = first_wv->contents; wv; wv = wv->next)
362             {
363               if (!strcmp (submenuTitle, wv->name))
364                 {
365                   [submenu fillWithWidgetValue: wv->contents];
366                   last_submenu = submenu;
367                   break;
368                 }
369             }
370         }
371       else
372         {
373           [menu fillWithWidgetValue: first_wv->contents];
374         }
376     }
377   else
378     {
379       static int n_previous_strings = 0;
380       static char previous_strings[100][10];
381       static struct frame *last_f = NULL;
382       int n;
383       Lisp_Object string;
385       wv = xmalloc_widget_value ();
386       wv->name = "menubar";
387       wv->value = 0;
388       wv->enabled = 1;
389       wv->button_type = BUTTON_TYPE_NONE;
390       wv->help = Qnil;
391       first_wv = wv;
393       /* Make widget-value tree w/ just the top level menu bar strings */
394       items = FRAME_MENU_BAR_ITEMS (f);
395       if (NILP (items))
396         {
397           free_menubar_widget_value_tree (first_wv);
398           [pool release];
399           unblock_input ();
400           return;
401         }
404       /* check if no change.. this mechanism is a bit rough, but ready */
405       n = ASIZE (items) / 4;
406       if (f == last_f && n_previous_strings == n)
407         {
408           for (i = 0; i<n; i++)
409             {
410               string = AREF (items, 4*i+1);
412               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
413                 continue;
414               if (NILP (string))
415                 {
416                   if (previous_strings[i][0])
417                     break;
418                   else
419                     continue;
420                 }
421               else if (memcmp (previous_strings[i], SDATA (string),
422                           min (10, SBYTES (string) + 1)))
423                 break;
424             }
426           if (i == n)
427             {
428               free_menubar_widget_value_tree (first_wv);
429               [pool release];
430               unblock_input ();
431               return;
432             }
433         }
435       [menu clear];
436       for (i = 0; i < ASIZE (items); i += 4)
437         {
438           string = AREF (items, i + 1);
439           if (NILP (string))
440             break;
442           if (n < 100)
443             memcpy (previous_strings[i/4], SDATA (string),
444                     min (10, SBYTES (string) + 1));
446           wv = xmalloc_widget_value ();
447           wv->name = SSDATA (string);
448           wv->value = 0;
449           wv->enabled = 1;
450           wv->button_type = BUTTON_TYPE_NONE;
451           wv->help = Qnil;
452           wv->call_data = (void *) (intptr_t) (-1);
454 #ifdef NS_IMPL_COCOA
455           /* we'll update the real copy under app menu when time comes */
456           if (!strcmp ("Services", wv->name))
457             {
458               /* but we need to make sure it will update on demand */
459               [svcsMenu setFrame: f];
460             }
461           else
462 #endif
463           [menu addSubmenuWithTitle: wv->name forFrame: f];
465           if (prev_wv)
466             prev_wv->next = wv;
467           else
468             first_wv->contents = wv;
469           prev_wv = wv;
470         }
472       last_f = f;
473       if (n < 100)
474         n_previous_strings = n;
475       else
476         n_previous_strings = 0;
478     }
479   free_menubar_widget_value_tree (first_wv);
482 #if NSMENUPROFILE
483   ftime (&tb);
484   t += 1000*tb.time+tb.millitm;
485   fprintf (stderr, "Menu update took %ld msec.\n", t);
486 #endif
488   /* set main menu */
489   if (needsSet)
490     [NSApp setMainMenu: menu];
492   [pool release];
493   unblock_input ();
498 /* Main emacs core entry point for menubar menus: called to indicate that the
499    frame's menus have changed, and the *step representation should be updated
500    from Lisp. */
501 void
502 set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
504   ns_update_menubar (f, deep_p, nil);
507 void
508 x_activate_menubar (struct frame *f)
510   NSArray *a = [[NSApp mainMenu] itemArray];
511   /* Update each submenu separately so ns_update_menubar doesn't reset
512      the delegate.  */
513   int i = 0;
514   while (i < [a count])
515     {
516       EmacsMenu *menu = (EmacsMenu *)[[a objectAtIndex:i] submenu];
517       const char *title = [[menu title] UTF8String];
518       if (strcmp (title, ns_get_pending_menu_title ()) == 0)
519         {
520           ns_update_menubar (f, true, menu);
521           break;
522         }
523       ++i;
524     }
525   ns_check_pending_open_menu ();
531 /* ==========================================================================
533     Menu: class implementation
535    ========================================================================== */
538 /* Menu that can define itself from Emacs "widget_value"s and will lazily
539    update itself when user clicked.  Based on Carbon/AppKit implementation
540    by Yamamoto Mitsuharu. */
541 @implementation EmacsMenu
543 /* override designated initializer */
544 - initWithTitle: (NSString *)title
546   if ((self = [super initWithTitle: title]))
547     [self setAutoenablesItems: NO];
548   return self;
552 /* used for top-level */
553 - initWithTitle: (NSString *)title frame: (struct frame *)f
555   [self initWithTitle: title];
556   frame = f;
557 #ifdef NS_IMPL_COCOA
558   [self setDelegate: self];
559 #endif
560   return self;
564 - (void)setFrame: (struct frame *)f
566   frame = f;
569 #ifdef NS_IMPL_COCOA
570 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
571 extern NSString *NSMenuDidBeginTrackingNotification;
572 #endif
573 #endif
575 #ifdef NS_IMPL_COCOA
576 -(void)trackingNotification:(NSNotification *)notification
578   /* Update menu in menuNeedsUpdate only while tracking menus.  */
579   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
580                   ? 1 : 0);
583 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
584 - (void)menuWillOpen:(NSMenu *)menu
586   ns_check_menu_open (menu);
588 #endif
590 #endif
592 /* delegate method called when a submenu is being opened: run a 'deep' call
593    to set_frame_menubar */
594 - (void)menuNeedsUpdate: (NSMenu *)menu
596   if (!FRAME_LIVE_P (frame))
597     return;
599   /* Cocoa/Carbon will request update on every keystroke
600      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
601      since key equivalents are handled through emacs.
602      On Leopard, even keystroke events generate SystemDefined event.
603      Third-party applications that enhance mouse / trackpad
604      interaction, or also VNC/Remote Desktop will send events
605      of type AppDefined rather than SysDefined.
606      Menus will fail to show up if they haven't been initialized.
607      AppDefined events may lack timing data.
609      Thus, we rely on the didBeginTrackingNotification notification
610      as above to indicate the need for updates.
611      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
612      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
613   */
614   if (trackingMenu == 0)
615     return;
616 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
617 #if ! defined(NS_IMPL_COCOA) || \
618   MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
619   /* Don't know how to do this for anything other than OSX >= 10.5
620      This is wrong, as it might run Lisp code in the event loop.  */
621   ns_update_menubar (frame, true, self);
622 #endif
626 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
628   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
629       && FRAME_NS_VIEW (SELECTED_FRAME ()))
630     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
631   return YES;
635 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
636    into an accelerator string.  We are only able to display a single character
637    for an accelerator, together with an optional modifier combination.  (Under
638    Carbon more control was possible, but in Cocoa multi-char strings passed to
639    NSMenuItem get ignored.  For now we try to display a super-single letter
640    combo, and return the others as strings to be appended to the item title.
641    (This is signaled by setting keyEquivModMask to 0 for now.) */
642 -(NSString *)parseKeyEquiv: (const char *)key
644   const char *tpos = key;
645   keyEquivModMask = NSCommandKeyMask;
647   if (!key || !strlen (key))
648     return @"";
650   while (*tpos == ' ' || *tpos == '(')
651     tpos++;
652   if ((*tpos == 's') && (*(tpos+1) == '-'))
653     {
654       return [NSString stringWithFormat: @"%c", tpos[2]];
655     }
656   keyEquivModMask = 0; /* signal */
657   return [NSString stringWithUTF8String: tpos];
661 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
663   NSMenuItem *item;
664   widget_value *wv = (widget_value *)wvptr;
666   if (menu_separator_name_p (wv->name))
667     {
668       item = [NSMenuItem separatorItem];
669       [self addItem: item];
670     }
671   else
672     {
673       NSString *title, *keyEq;
674       title = [NSString stringWithUTF8String: wv->name];
675       if (title == nil)
676         title = @"< ? >";  /* (get out in the open so we know about it) */
678       keyEq = [self parseKeyEquiv: wv->key];
679 #ifdef NS_IMPL_COCOA
680       /* OS X just ignores modifier strings longer than one character */
681       if (keyEquivModMask == 0)
682         title = [title stringByAppendingFormat: @" (%@)", keyEq];
683 #endif
685       item = [self addItemWithTitle: (NSString *)title
686                              action: @selector (menuDown:)
687                       keyEquivalent: keyEq];
688       [item setKeyEquivalentModifierMask: keyEquivModMask];
690       [item setEnabled: wv->enabled];
692       /* Draw radio buttons and tickboxes */
693       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
694                            wv->button_type == BUTTON_TYPE_RADIO))
695         [item setState: NSOnState];
696       else
697         [item setState: NSOffState];
699       [item setTag: (NSInteger)wv->call_data];
700     }
702   return item;
706 /* convenience */
707 -(void)clear
709   int n;
711   for (n = [self numberOfItems]-1; n >= 0; n--)
712     {
713       NSMenuItem *item = [self itemAtIndex: n];
714       NSString *title = [item title];
715       if (([title length] == 0  /* OSX 10.5 */
716            || [ns_app_name isEqualToString: title]  /* from 10.6 on */
717            || [@"Apple" isEqualToString: title]) /* older */
718           && ![item isSeparatorItem])
719         continue;
720       [self removeItemAtIndex: n];
721     }
725 - (void)fillWithWidgetValue: (void *)wvptr
727   widget_value *wv = (widget_value *)wvptr;
729   /* clear existing contents */
730   [self setMenuChangedMessagesEnabled: NO];
731   [self clear];
733   /* add new contents */
734   for (; wv != NULL; wv = wv->next)
735     {
736       NSMenuItem *item = [self addItemWithWidgetValue: wv];
738       if (wv->contents)
739         {
740           EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
742           [self setSubmenu: submenu forItem: item];
743           [submenu fillWithWidgetValue: wv->contents];
744           [submenu release];
745           [item setAction: nil];
746         }
747     }
749   [self setMenuChangedMessagesEnabled: YES];
750 #ifdef NS_IMPL_GNUSTEP
751   if ([[self window] isVisible])
752     [self sizeToFit];
753 #endif
757 /* adds an empty submenu and returns it */
758 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
760   NSString *titleStr = [NSString stringWithUTF8String: title];
761   NSMenuItem *item = [self addItemWithTitle: titleStr
762                                      action: nil /*@selector (menuDown:) */
763                               keyEquivalent: @""];
764   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
765   [self setSubmenu: submenu forItem: item];
766   [submenu release];
767   return submenu;
770 /* run a menu in popup mode */
771 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
772                  keymaps: (bool)keymaps
774   EmacsView *view = FRAME_NS_VIEW (f);
775   NSEvent *e, *event;
776   long retVal;
778 /*   p = [view convertPoint:p fromView: nil]; */
779   p.y = NSHeight ([view frame]) - p.y;
780   e = [[view window] currentEvent];
781    event = [NSEvent mouseEventWithType: NSRightMouseDown
782                               location: p
783                          modifierFlags: 0
784                              timestamp: [e timestamp]
785                           windowNumber: [[view window] windowNumber]
786                                context: [e context]
787                            eventNumber: 0/*[e eventNumber] */
788                             clickCount: 1
789                               pressure: 0];
791   context_menu_value = -1;
792   [NSMenu popUpContextMenu: self withEvent: event forView: view];
793   retVal = context_menu_value;
794   context_menu_value = 0;
795   return retVal > 0
796       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
797       : Qnil;
800 @end  /* EmacsMenu */
804 /* ==========================================================================
806     Context Menu: implementing functions
808    ========================================================================== */
810 Lisp_Object
811 ns_menu_show (FRAME_PTR f, int x, int y, bool for_click, bool keymaps,
812               Lisp_Object title, const char **error)
814   EmacsMenu *pmenu;
815   NSPoint p;
816   Lisp_Object tem;
817   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
818   widget_value *wv, *first_wv = 0;
820   p.x = x; p.y = y;
822   /* now parse stage 2 as in ns_update_menubar */
823   wv = xmalloc_widget_value ();
824   wv->name = "contextmenu";
825   wv->value = 0;
826   wv->enabled = 1;
827   wv->button_type = BUTTON_TYPE_NONE;
828   wv->help = Qnil;
829   first_wv = wv;
831 #if 0
832   /* FIXME: a couple of one-line differences prevent reuse */
833   wv = digest_single_submenu (0, menu_items_used, 0);
834 #else
835   {
836   widget_value *save_wv = 0, *prev_wv = 0;
837   widget_value **submenu_stack
838     = alloca (menu_items_used * sizeof *submenu_stack);
839 /*   Lisp_Object *subprefix_stack
840        = alloca (menu_items_used * sizeof *subprefix_stack); */
841   int submenu_depth = 0;
842   int first_pane = 1;
843   int i;
845   /* Loop over all panes and items, filling in the tree.  */
846   i = 0;
847   while (i < menu_items_used)
848     {
849       if (EQ (AREF (menu_items, i), Qnil))
850         {
851           submenu_stack[submenu_depth++] = save_wv;
852           save_wv = prev_wv;
853           prev_wv = 0;
854           first_pane = 1;
855           i++;
856         }
857       else if (EQ (AREF (menu_items, i), Qlambda))
858         {
859           prev_wv = save_wv;
860           save_wv = submenu_stack[--submenu_depth];
861           first_pane = 0;
862           i++;
863         }
864       else if (EQ (AREF (menu_items, i), Qt)
865                && submenu_depth != 0)
866         i += MENU_ITEMS_PANE_LENGTH;
867       /* Ignore a nil in the item list.
868          It's meaningful only for dialog boxes.  */
869       else if (EQ (AREF (menu_items, i), Qquote))
870         i += 1;
871       else if (EQ (AREF (menu_items, i), Qt))
872         {
873           /* Create a new pane.  */
874           Lisp_Object pane_name, prefix;
875           const char *pane_string;
877           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
878           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
880 #ifndef HAVE_MULTILINGUAL_MENU
881           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
882             {
883               pane_name = ENCODE_MENU_STRING (pane_name);
884               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
885             }
886 #endif
887           pane_string = (NILP (pane_name)
888                          ? "" : SSDATA (pane_name));
889           /* If there is just one top-level pane, put all its items directly
890              under the top-level menu.  */
891           if (menu_items_n_panes == 1)
892             pane_string = "";
894           /* If the pane has a meaningful name,
895              make the pane a top-level menu item
896              with its items as a submenu beneath it.  */
897           if (!keymaps && strcmp (pane_string, ""))
898             {
899               wv = xmalloc_widget_value ();
900               if (save_wv)
901                 save_wv->next = wv;
902               else
903                 first_wv->contents = wv;
904               wv->name = pane_string;
905               if (keymaps && !NILP (prefix))
906                 wv->name++;
907               wv->value = 0;
908               wv->enabled = 1;
909               wv->button_type = BUTTON_TYPE_NONE;
910               wv->help = Qnil;
911               save_wv = wv;
912               prev_wv = 0;
913             }
914           else if (first_pane)
915             {
916               save_wv = wv;
917               prev_wv = 0;
918             }
919           first_pane = 0;
920           i += MENU_ITEMS_PANE_LENGTH;
921         }
922       else
923         {
924           /* Create a new item within current pane.  */
925           Lisp_Object item_name, enable, descrip, def, type, selected, help;
926           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
927           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
928           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
929           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
930           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
931           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
932           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
934 #ifndef HAVE_MULTILINGUAL_MENU
935           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
936             {
937               item_name = ENCODE_MENU_STRING (item_name);
938               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
939             }
941           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
942             {
943               descrip = ENCODE_MENU_STRING (descrip);
944               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
945             }
946 #endif /* not HAVE_MULTILINGUAL_MENU */
948           wv = xmalloc_widget_value ();
949           if (prev_wv)
950             prev_wv->next = wv;
951           else
952             save_wv->contents = wv;
953           wv->name = SSDATA (item_name);
954           if (!NILP (descrip))
955             wv->key = SSDATA (descrip);
956           wv->value = 0;
957           /* If this item has a null value,
958              make the call_data null so that it won't display a box
959              when the mouse is on it.  */
960           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
961           wv->enabled = !NILP (enable);
963           if (NILP (type))
964             wv->button_type = BUTTON_TYPE_NONE;
965           else if (EQ (type, QCtoggle))
966             wv->button_type = BUTTON_TYPE_TOGGLE;
967           else if (EQ (type, QCradio))
968             wv->button_type = BUTTON_TYPE_RADIO;
969           else
970             emacs_abort ();
972           wv->selected = !NILP (selected);
974           if (! STRINGP (help))
975             help = Qnil;
977           wv->help = help;
979           prev_wv = wv;
981           i += MENU_ITEMS_ITEM_LENGTH;
982         }
983     }
984   }
985 #endif
987   if (!NILP (title))
988     {
989       widget_value *wv_title = xmalloc_widget_value ();
990       widget_value *wv_sep = xmalloc_widget_value ();
992       /* Maybe replace this separator with a bitmap or owner-draw item
993          so that it looks better.  Having two separators looks odd.  */
994       wv_sep->name = "--";
995       wv_sep->next = first_wv->contents;
996       wv_sep->help = Qnil;
998 #ifndef HAVE_MULTILINGUAL_MENU
999       if (STRING_MULTIBYTE (title))
1000         title = ENCODE_MENU_STRING (title);
1001 #endif
1003       wv_title->name = SSDATA (title);
1004       wv_title->enabled = NO;
1005       wv_title->button_type = BUTTON_TYPE_NONE;
1006       wv_title->help = Qnil;
1007       wv_title->next = wv_sep;
1008       first_wv->contents = wv_title;
1009     }
1011   pmenu = [[EmacsMenu alloc] initWithTitle:
1012                                [NSString stringWithUTF8String: SSDATA (title)]];
1013   [pmenu fillWithWidgetValue: first_wv->contents];
1014   free_menubar_widget_value_tree (first_wv);
1015   unbind_to (specpdl_count, Qnil);
1017   popup_activated_flag = 1;
1018   tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1019   popup_activated_flag = 0;
1020   [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1022   return tem;
1026 /* ==========================================================================
1028     Toolbar: externally-called functions
1030    ========================================================================== */
1032 void
1033 free_frame_tool_bar (FRAME_PTR f)
1034 /* --------------------------------------------------------------------------
1035     Under NS we just hide the toolbar until it might be needed again.
1036    -------------------------------------------------------------------------- */
1038   block_input ();
1039   [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1040   FRAME_TOOLBAR_HEIGHT (f) = 0;
1041   unblock_input ();
1044 void
1045 update_frame_tool_bar (FRAME_PTR f)
1046 /* --------------------------------------------------------------------------
1047     Update toolbar contents
1048    -------------------------------------------------------------------------- */
1050   int i;
1051   EmacsView *view = FRAME_NS_VIEW (f);
1052   NSWindow *window = [view window];
1053   EmacsToolbar *toolbar = [view toolbar];
1055   block_input ();
1056   [toolbar clearActive];
1058   /* update EmacsToolbar as in GtkUtils, build items list */
1059   for (i = 0; i < f->n_tool_bar_items; ++i)
1060     {
1061 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1062                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1064       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1065       int idx;
1066       ptrdiff_t img_id;
1067       struct image *img;
1068       Lisp_Object image;
1069       Lisp_Object helpObj;
1070       const char *helpText;
1072       /* If image is a vector, choose the image according to the
1073          button state.  */
1074       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1075       if (VECTORP (image))
1076         {
1077           /* NS toolbar auto-computes disabled and selected images */
1078           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1079           eassert (ASIZE (image) >= idx);
1080           image = AREF (image, idx);
1081         }
1082       else
1083         {
1084           idx = -1;
1085         }
1086       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1087       if (NILP (helpObj))
1088         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1089       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1091       /* Ignore invalid image specifications.  */
1092       if (!valid_image_p (image))
1093         {
1094           /* Don't log anything, GNUS makes invalid images all the time.  */
1095           continue;
1096         }
1098       img_id = lookup_image (f, image);
1099       img = IMAGE_FROM_ID (f, img_id);
1100       prepare_image_for_display (f, img);
1102       if (img->load_failed_p || img->pixmap == nil)
1103         {
1104           NSLog (@"Could not prepare toolbar image for display.");
1105           continue;
1106         }
1108       [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1109                                enabled: enabled_p];
1110 #undef TOOLPROP
1111     }
1113   if (![toolbar isVisible])
1114       [toolbar setVisible: YES];
1116   if ([toolbar changed])
1117     {
1118       /* inform app that toolbar has changed */
1119       NSDictionary *dict = [toolbar configurationDictionary];
1120       NSMutableDictionary *newDict = [dict mutableCopy];
1121       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1122       id key;
1123       while ((key = [keys nextObject]) != nil)
1124         {
1125           NSObject *val = [dict objectForKey: key];
1126           if ([val isKindOfClass: [NSArray class]])
1127             {
1128               [newDict setObject:
1129                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1130                           forKey: key];
1131               break;
1132             }
1133         }
1134       [toolbar setConfigurationFromDictionary: newDict];
1135       [newDict release];
1136     }
1138   FRAME_TOOLBAR_HEIGHT (f) =
1139     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1140     - FRAME_NS_TITLEBAR_HEIGHT (f);
1141     if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1142       FRAME_TOOLBAR_HEIGHT (f) = 0;
1143     unblock_input ();
1147 /* ==========================================================================
1149     Toolbar: class implementation
1151    ========================================================================== */
1153 @implementation EmacsToolbar
1155 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1157   self = [super initWithIdentifier: identifier];
1158   emacsView = view;
1159   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1160   [self setSizeMode: NSToolbarSizeModeSmall];
1161   [self setDelegate: self];
1162   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1163   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1164   prevEnablement = enablement = 0L;
1165   return self;
1168 - (void)dealloc
1170   [prevIdentifiers release];
1171   [activeIdentifiers release];
1172   [identifierToItem release];
1173   [super dealloc];
1176 - (void) clearActive
1178   [prevIdentifiers release];
1179   prevIdentifiers = [activeIdentifiers copy];
1180   [activeIdentifiers removeAllObjects];
1181   prevEnablement = enablement;
1182   enablement = 0L;
1185 - (BOOL) changed
1187   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1188     enablement == prevEnablement ? NO : YES;
1191 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1192                         helpText: (const char *)help enabled: (BOOL)enabled
1194   /* 1) come up w/identifier */
1195   NSString *identifier
1196       = [NSString stringWithFormat: @"%u", [img hash]];
1198   /* 2) create / reuse item */
1199   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1200   if (item == nil)
1201     {
1202       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1203                autorelease];
1204       [item setImage: img];
1205       [item setToolTip: [NSString stringWithUTF8String: help]];
1206       [item setTarget: emacsView];
1207       [item setAction: @selector (toolbarClicked:)];
1208     }
1210   [item setTag: idx];
1211   [item setEnabled: enabled];
1213   /* 3) update state */
1214   [identifierToItem setObject: item forKey: identifier];
1215   [activeIdentifiers addObject: identifier];
1216   enablement = (enablement << 1) | (enabled == YES);
1219 /* This overrides super's implementation, which automatically sets
1220    all items to enabled state (for some reason). */
1221 - (void)validateVisibleItems { }
1224 /* delegate methods */
1226 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1227       itemForItemIdentifier: (NSString *)itemIdentifier
1228   willBeInsertedIntoToolbar: (BOOL)flag
1230   /* look up NSToolbarItem by identifier and return... */
1231   return [identifierToItem objectForKey: itemIdentifier];
1234 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1236   /* return entire set.. */
1237   return activeIdentifiers;
1240 /* for configuration palette (not yet supported) */
1241 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1243   /* return entire set... */
1244   return [identifierToItem allKeys];
1247 /* optional and unneeded */
1248 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1249 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1250 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1252 @end  /* EmacsToolbar */
1256 /* ==========================================================================
1258     Tooltip: class implementation
1260    ========================================================================== */
1262 /* Needed because NeXTstep does not provide enough control over tooltip
1263    display. */
1264 @implementation EmacsTooltip
1266 - init
1268   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1269                                             blue: 0.792 alpha: 0.95];
1270   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1271   NSFont *sfont = [font screenFont];
1272   int height = [sfont ascender] - [sfont descender];
1273 /*[font boundingRectForFont].size.height; */
1274   NSRect r = NSMakeRect (0, 0, 100, height+6);
1276   textField = [[NSTextField alloc] initWithFrame: r];
1277   [textField setFont: font];
1278   [textField setBackgroundColor: col];
1280   [textField setEditable: NO];
1281   [textField setSelectable: NO];
1282   [textField setBordered: NO];
1283   [textField setBezeled: NO];
1284   [textField setDrawsBackground: YES];
1286   win = [[NSWindow alloc]
1287             initWithContentRect: [textField frame]
1288                       styleMask: 0
1289                         backing: NSBackingStoreBuffered
1290                           defer: YES];
1291   [win setHasShadow: YES];
1292   [win setReleasedWhenClosed: NO];
1293   [win setDelegate: self];
1294   [[win contentView] addSubview: textField];
1295 /*  [win setBackgroundColor: col]; */
1296   [win setOpaque: NO];
1298   return self;
1301 - (void) dealloc
1303   [win close];
1304   [win release];
1305   [textField release];
1306   [super dealloc];
1309 - (void) setText: (char *)text
1311   NSString *str = [NSString stringWithUTF8String: text];
1312   NSRect r  = [textField frame];
1313   NSSize tooltipDims;
1315   [textField setStringValue: str];
1316   tooltipDims = [[textField cell] cellSize];
1318   r.size.width = tooltipDims.width;
1319   r.size.height = tooltipDims.height;
1320   [textField setFrame: r];
1323 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1325   NSRect wr = [win frame];
1327   wr.origin = NSMakePoint (x, y);
1328   wr.size = [textField frame].size;
1330   [win setFrame: wr display: YES];
1331   [win setLevel: NSPopUpMenuWindowLevel];
1332   [win orderFront: self];
1333   [win display];
1334   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1335                                          selector: @selector (hide)
1336                                          userInfo: nil repeats: NO];
1337   [timer retain];
1340 - (void) hide
1342   [win close];
1343   if (timer != nil)
1344     {
1345       if ([timer isValid])
1346         [timer invalidate];
1347       [timer release];
1348       timer = nil;
1349     }
1352 - (BOOL) isActive
1354   return timer != nil;
1357 - (NSRect) frame
1359   return [textField frame];
1362 @end  /* EmacsTooltip */
1366 /* ==========================================================================
1368     Popup Dialog: implementing functions
1370    ========================================================================== */
1372 struct Popdown_data
1374   NSAutoreleasePool *pool;
1375   EmacsDialogPanel *dialog;
1378 static Lisp_Object
1379 pop_down_menu (Lisp_Object arg)
1381   struct Popdown_data *unwind_data = XSAVE_POINTER (arg, 0);
1383   block_input ();
1384   if (popup_activated_flag)
1385     {
1386       EmacsDialogPanel *panel = unwind_data->dialog;
1387       popup_activated_flag = 0;
1388       [panel close];
1389       [unwind_data->pool release];
1390       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1391     }
1393   xfree (unwind_data);
1394   unblock_input ();
1396   return Qnil;
1400 Lisp_Object
1401 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1403   id dialog;
1404   Lisp_Object window, tem, title;
1405   struct frame *f;
1406   NSPoint p;
1407   BOOL isQ;
1408   NSAutoreleasePool *pool;
1410   NSTRACE (x-popup-dialog);
1412   isQ = NILP (header);
1414   if (EQ (position, Qt)
1415       || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1416                                || EQ (XCAR (position), Qtool_bar))))
1417     {
1418       window = selected_window;
1419     }
1420   else if (CONSP (position))
1421     {
1422       Lisp_Object tem;
1423       tem = Fcar (position);
1424       if (XTYPE (tem) == Lisp_Cons)
1425         window = Fcar (Fcdr (position));
1426       else
1427         {
1428           tem = Fcar (Fcdr (position));  /* EVENT_START (position) */
1429           window = Fcar (tem);       /* POSN_WINDOW (tem) */
1430         }
1431     }
1432   else if (WINDOWP (position) || FRAMEP (position))
1433     {
1434       window = position;
1435     }
1436   else
1437     window = Qnil;
1439   if (FRAMEP (window))
1440     f = XFRAME (window);
1441   else if (WINDOWP (window))
1442     {
1443       CHECK_LIVE_WINDOW (window);
1444       f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1445     }
1446   else
1447     CHECK_WINDOW (window);
1449   check_window_system (f);
1451   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1452   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1454   title = Fcar (contents);
1455   CHECK_STRING (title);
1457   if (NILP (Fcar (Fcdr (contents))))
1458     /* No buttons specified, add an "Ok" button so users can pop down
1459        the dialog.  */
1460     contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1462   block_input ();
1463   pool = [[NSAutoreleasePool alloc] init];
1464   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1465                                            isQuestion: isQ];
1467   {
1468     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1469     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1471     unwind_data->pool = pool;
1472     unwind_data->dialog = dialog;
1474     record_unwind_protect (pop_down_menu, make_save_pointer (unwind_data));
1475     popup_activated_flag = 1;
1476     tem = [dialog runDialogAt: p];
1477     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1478   }
1480   unblock_input ();
1482   return tem;
1486 /* ==========================================================================
1488     Popup Dialog: class implementation
1490    ========================================================================== */
1492 @interface FlippedView : NSView
1495 @end
1497 @implementation FlippedView
1498 - (BOOL)isFlipped
1500   return YES;
1502 @end
1504 @implementation EmacsDialogPanel
1506 #define SPACER          8.0
1507 #define ICONSIZE        64.0
1508 #define TEXTHEIGHT      20.0
1509 #define MINCELLWIDTH    90.0
1511 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1512               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1514   NSSize spacing = {SPACER, SPACER};
1515   NSRect area;
1516   id cell;
1517   NSImageView *imgView;
1518   FlippedView *contentView;
1519   NSImage *img;
1521   dialog_return   = Qundefined;
1522   button_values   = NULL;
1523   area.origin.x   = 3*SPACER;
1524   area.origin.y   = 2*SPACER;
1525   area.size.width = ICONSIZE;
1526   area.size.height= ICONSIZE;
1527   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1528   [img setScalesWhenResized: YES];
1529   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1530   imgView = [[NSImageView alloc] initWithFrame: area];
1531   [imgView setImage: img];
1532   [imgView setEditable: NO];
1533   [img autorelease];
1534   [imgView autorelease];
1536   aStyle = NSTitledWindowMask;
1537   flag = YES;
1538   rows = 0;
1539   cols = 1;
1540   [super initWithContentRect: contentRect styleMask: aStyle
1541                      backing: backingType defer: flag];
1542   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1543   [contentView autorelease];
1545   [self setContentView: contentView];
1547   [[self contentView] setAutoresizesSubviews: YES];
1549   [[self contentView] addSubview: imgView];
1550   [self setTitle: @""];
1552   area.origin.x   += ICONSIZE+2*SPACER;
1553 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1554   area.size.width = 400;
1555   area.size.height= TEXTHEIGHT;
1556   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1557   [[self contentView] addSubview: command];
1558   [command setStringValue: ns_app_name];
1559   [command setDrawsBackground: NO];
1560   [command setBezeled: NO];
1561   [command setSelectable: NO];
1562   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1564 /*  area.origin.x   = ICONSIZE+2*SPACER;
1565   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1566   area.size.width = 400;
1567   area.size.height= 2;
1568   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1569   [[self contentView] addSubview: tem];
1570   [tem setTitlePosition: NSNoTitle];
1571   [tem setAutoresizingMask: NSViewWidthSizable];*/
1573 /*  area.origin.x = ICONSIZE+2*SPACER; */
1574   area.origin.y += TEXTHEIGHT+SPACER;
1575   area.size.width = 400;
1576   area.size.height= TEXTHEIGHT;
1577   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1578   [[self contentView] addSubview: title];
1579   [title setDrawsBackground: NO];
1580   [title setBezeled: NO];
1581   [title setSelectable: NO];
1582   [title setFont: [NSFont systemFontOfSize: 11.0]];
1584   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1585   [cell setBordered: NO];
1586   [cell setEnabled: NO];
1587   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1588   [cell setBezelStyle: NSRoundedBezelStyle];
1590   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1591                                       mode: NSHighlightModeMatrix
1592                                  prototype: cell
1593                               numberOfRows: 0
1594                            numberOfColumns: 1];
1595   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1596                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1597   [matrix setIntercellSpacing: spacing];
1598   [matrix autorelease];
1600   [[self contentView] addSubview: matrix];
1601   [self setOneShot: YES];
1602   [self setReleasedWhenClosed: YES];
1603   [self setHidesOnDeactivate: YES];
1604   [self setStyleMask:
1605           NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask];
1607   return self;
1611 - (BOOL)windowShouldClose: (id)sender
1613   window_closed = YES;
1614   [NSApp stop:self];
1615   return NO;
1618 - (void)dealloc
1620   xfree (button_values);
1621   [super dealloc];
1624 - (void)process_dialog: (Lisp_Object) list
1626   Lisp_Object item, lst = list;
1627   int row = 0;
1628   int buttons = 0, btnnr = 0;
1630   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1631     {
1632       item = XCAR (list);
1633       if (XTYPE (item) == Lisp_Cons)
1634         ++buttons;
1635     }
1637   if (buttons > 0)
1638     button_values = (Lisp_Object *) xmalloc (buttons * sizeof (*button_values));
1640   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1641     {
1642       item = XCAR (list);
1643       if (XTYPE (item) == Lisp_String)
1644         {
1645           [self addString: SSDATA (item) row: row++];
1646         }
1647       else if (XTYPE (item) == Lisp_Cons)
1648         {
1649           button_values[btnnr] = XCDR (item);
1650           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1651           ++btnnr;
1652         }
1653       else if (NILP (item))
1654         {
1655           [self addSplit];
1656           row = 0;
1657         }
1658     }
1662 - (void)addButton: (char *)str value: (int)tag row: (int)row
1664   id cell;
1666   if (row >= rows)
1667     {
1668       [matrix addRow];
1669       rows++;
1670     }
1671   cell = [matrix cellAtRow: row column: cols-1];
1672   [cell setTarget: self];
1673   [cell setAction: @selector (clicked: )];
1674   [cell setTitle: [NSString stringWithUTF8String: str]];
1675   [cell setTag: tag];
1676   [cell setBordered: YES];
1677   [cell setEnabled: YES];
1681 - (void)addString: (char *)str row: (int)row
1683   id cell;
1685   if (row >= rows)
1686     {
1687       [matrix addRow];
1688       rows++;
1689     }
1690   cell = [matrix cellAtRow: row column: cols-1];
1691   [cell setTitle: [NSString stringWithUTF8String: str]];
1692   [cell setBordered: YES];
1693   [cell setEnabled: NO];
1697 - (void)addSplit
1699   [matrix addColumn];
1700   cols++;
1704 - (void)clicked: sender
1706   NSArray *sellist = nil;
1707   EMACS_INT seltag;
1709   sellist = [sender selectedCells];
1710   if ([sellist count] < 1)
1711     return;
1713   seltag = [[sellist objectAtIndex: 0] tag];
1714   dialog_return = button_values[seltag];
1715   [NSApp stop:self];
1719 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1721   Lisp_Object head;
1722   [super init];
1724   if (XTYPE (contents) == Lisp_Cons)
1725     {
1726       head = Fcar (contents);
1727       [self process_dialog: Fcdr (contents)];
1728     }
1729   else
1730     head = contents;
1732   if (XTYPE (head) == Lisp_String)
1733       [title setStringValue:
1734                  [NSString stringWithUTF8String: SSDATA (head)]];
1735   else if (isQ == YES)
1736       [title setStringValue: @"Question"];
1737   else
1738       [title setStringValue: @"Information"];
1740   {
1741     int i;
1742     NSRect r, s, t;
1744     if (cols == 1 && rows > 1)  /* Never told where to split */
1745       {
1746         [matrix addColumn];
1747         for (i = 0; i < rows/2; i++)
1748           {
1749             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1750                       atRow: i column: 1];
1751             [matrix removeRow: (rows+1)/2];
1752           }
1753       }
1755     [matrix sizeToFit];
1756     {
1757       NSSize csize = [matrix cellSize];
1758       if (csize.width < MINCELLWIDTH)
1759         {
1760           csize.width = MINCELLWIDTH;
1761           [matrix setCellSize: csize];
1762           [matrix sizeToCells];
1763         }
1764     }
1766     [title sizeToFit];
1767     [command sizeToFit];
1769     t = [matrix frame];
1770     r = [title frame];
1771     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1772       {
1773         t.origin.x   = r.origin.x;
1774         t.size.width = r.size.width;
1775       }
1776     r = [command frame];
1777     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1778       {
1779         t.origin.x   = r.origin.x;
1780         t.size.width = r.size.width;
1781       }
1783     r = [self frame];
1784     s = [(NSView *)[self contentView] frame];
1785     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1786     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1787     [self setFrame: r display: NO];
1788   }
1790   return self;
1795 - (void)timeout_handler: (NSTimer *)timedEntry
1797   NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1798                             location: NSMakePoint (0, 0)
1799                        modifierFlags: 0
1800                            timestamp: 0
1801                         windowNumber: [[NSApp mainWindow] windowNumber]
1802                              context: [NSApp context]
1803                              subtype: 0
1804                                data1: 0
1805                                data2: 0];
1807   timer_fired = YES;
1808   /* We use sto because stopModal/abortModal out of the main loop does not
1809      seem to work in 10.6.  But as we use stop we must send a real event so
1810      the stop is seen and acted upon.  */
1811   [NSApp stop:self];
1812   [NSApp postEvent: nxev atStart: NO];
1815 - (Lisp_Object)runDialogAt: (NSPoint)p
1817   Lisp_Object ret = Qundefined;
1819   while (popup_activated_flag)
1820     {
1821       NSTimer *tmo = nil;
1822       EMACS_TIME next_time = timer_check ();
1824       if (EMACS_TIME_VALID_P (next_time))
1825         {
1826           double time = EMACS_TIME_TO_DOUBLE (next_time);
1827           tmo = [NSTimer timerWithTimeInterval: time
1828                                         target: self
1829                                       selector: @selector (timeout_handler:)
1830                                       userInfo: 0
1831                                        repeats: NO];
1832           [[NSRunLoop currentRunLoop] addTimer: tmo
1833                                        forMode: NSModalPanelRunLoopMode];
1834         }
1835       timer_fired = NO;
1836       dialog_return = Qundefined;
1837       [NSApp runModalForWindow: self];
1838       ret = dialog_return;
1839       if (! timer_fired)
1840         {
1841           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1842           break;
1843         }
1844     }
1846   if (EQ (ret, Qundefined) && window_closed)
1847     /* Make close button pressed equivalent to C-g.  */
1848     Fsignal (Qquit, Qnil);
1850   return ret;
1853 @end
1856 /* ==========================================================================
1858     Lisp definitions
1860    ========================================================================== */
1862 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1863        doc: /* Cause the NS menu to be re-calculated.  */)
1864      (void)
1866   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1867   return Qnil;
1871 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1872        doc: /* Pop up a dialog box and return user's selection.
1873 POSITION specifies which frame to use.
1874 This is normally a mouse button event or a window or frame.
1875 If POSITION is t, it means to use the frame the mouse is on.
1876 The dialog box appears in the middle of the specified frame.
1878 CONTENTS specifies the alternatives to display in the dialog box.
1879 It is a list of the form (DIALOG ITEM1 ITEM2...).
1880 Each ITEM is a cons cell (STRING . VALUE).
1881 The return value is VALUE from the chosen item.
1883 An ITEM may also be just a string--that makes a nonselectable item.
1884 An ITEM may also be nil--that means to put all preceding items
1885 on the left of the dialog box and all following items on the right.
1886 \(By default, approximately half appear on each side.)
1888 If HEADER is non-nil, the frame title for the box is "Information",
1889 otherwise it is "Question".
1891 If the user gets rid of the dialog box without making a valid choice,
1892 for instance using the window manager, then this produces a quit and
1893 `x-popup-dialog' does not return.  */)
1894      (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1896   return ns_popup_dialog (position, contents, header);
1899 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1900        doc: /* Return t if a menu or popup dialog is active.  */)
1901      (void)
1903   return popup_activated () ? Qt : Qnil;
1906 /* ==========================================================================
1908     Lisp interface declaration
1910    ========================================================================== */
1912 void
1913 syms_of_nsmenu (void)
1915 #ifndef NS_IMPL_COCOA
1916   /* Don't know how to keep track of this in Next/Open/Gnustep.  Always
1917      update menus there.  */
1918   trackingMenu = 1;
1919 #endif
1920   defsubr (&Sx_popup_dialog);
1921   defsubr (&Sns_reset_menu);
1922   defsubr (&Smenu_or_popup_active_p);
1924   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1925   staticpro (&Qdebug_on_next_call);