* lisp/vc/vc-filewise.el: Comment fixes.
[emacs.git] / src / nsmenu.m
blob0e8b68b38f198b01b0b6bfd7dec1d15a0ceba5f3
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2    Copyright (C) 2007-2014 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 #if 0
49 int menu_trace_num = 0;
50 #define NSTRACE(x)        fprintf (stderr, "%s:%d: [%d] " #x "\n",        \
51                                 __FILE__, __LINE__, ++menu_trace_num)
52 #else
53 #define NSTRACE(x)
54 #endif
56 #if 0
57 /* Include lisp -> C common menu parsing code */
58 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
59 #include "nsmenu_common.c"
60 #endif
62 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
63 extern Lisp_Object QCtoggle, QCradio;
65 Lisp_Object Qdebug_on_next_call;
66 extern Lisp_Object Qoverriding_local_map, Qoverriding_terminal_local_map;
68 extern long context_menu_value;
69 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
71 /* Nonzero means a menu is currently active.  */
72 static int popup_activated_flag;
74 /* Nonzero means we are tracking and updating menus.  */
75 static int trackingMenu;
78 /* NOTE: toolbar implementation is at end,
79   following complete menu implementation. */
82 /* ==========================================================================
84     Menu: Externally-called functions
86    ========================================================================== */
89 /* Supposed to discard menubar and free storage.  Since we share the
90    menubar among frames and update its context for the focused window,
91    there is nothing to do here. */
92 void
93 free_frame_menubar (struct frame *f)
95   return;
99 int
100 popup_activated (void)
102   return popup_activated_flag;
106 /* --------------------------------------------------------------------------
107     Update menubar.  Three cases:
108     1) ! deep_p, submenu = nil: Fresh switch onto a frame -- either set up
109        just top-level menu strings (OS X), or goto case (2) (GNUstep).
110     2) deep_p, submenu = nil: Recompute all submenus.
111     3) deep_p, submenu = non-nil: Update contents of a single submenu.
112    -------------------------------------------------------------------------- */
113 static void
114 ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu)
116   NSAutoreleasePool *pool;
117   id menu = [NSApp mainMenu];
118   static EmacsMenu *last_submenu = nil;
119   BOOL needsSet = NO;
120   bool owfi;
121   Lisp_Object items;
122   widget_value *wv, *first_wv, *prev_wv = 0;
123   int i;
125 #if NSMENUPROFILE
126   struct timeb tb;
127   long t;
128 #endif
130   NSTRACE (ns_update_menubar);
132   if (f != SELECTED_FRAME ())
133       return;
134   XSETFRAME (Vmenu_updating_frame, f);
135 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
137   block_input ();
138   pool = [[NSAutoreleasePool alloc] init];
140   /* Menu may have been created automatically; if so, discard it. */
141   if ([menu isKindOfClass: [EmacsMenu class]] == NO)
142     {
143       [menu release];
144       menu = nil;
145     }
147   if (menu == nil)
148     {
149       menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
150       needsSet = YES;
151     }
152   else
153     {  /* close up anything on there */
154       id attMenu = [menu attachedMenu];
155       if (attMenu != nil)
156         [attMenu close];
157     }
159 #if NSMENUPROFILE
160   ftime (&tb);
161   t = -(1000*tb.time+tb.millitm);
162 #endif
164 #ifdef NS_IMPL_GNUSTEP
165   deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
166 #endif
168   if (deep_p)
169     {
170       /* Fully parse one or more of the submenus. */
171       int n = 0;
172       int *submenu_start, *submenu_end;
173       bool *submenu_top_level_items;
174       int *submenu_n_panes;
175       struct buffer *prev = current_buffer;
176       Lisp_Object buffer;
177       ptrdiff_t specpdl_count = SPECPDL_INDEX ();
178       int previous_menu_items_used = f->menu_bar_items_used;
179       Lisp_Object *previous_items
180         = alloca (previous_menu_items_used * sizeof *previous_items);
182       /* lisp preliminaries */
183       buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->contents;
184       specbind (Qinhibit_quit, Qt);
185       specbind (Qdebug_on_next_call, Qnil);
186       record_unwind_save_match_data ();
187       if (NILP (Voverriding_local_map_menu_flag))
188         {
189           specbind (Qoverriding_terminal_local_map, Qnil);
190           specbind (Qoverriding_local_map, Qnil);
191         }
192       set_buffer_internal_1 (XBUFFER (buffer));
194       /* TODO: for some reason this is not needed in other terms,
195            but some menu updates call Info-extract-pointer which causes
196            abort-on-error if waiting-for-input.  Needs further investigation. */
197       owfi = waiting_for_input;
198       waiting_for_input = 0;
200       /* lucid hook and possible reset */
201       safe_run_hooks (Qactivate_menubar_hook);
202       if (! NILP (Vlucid_menu_bar_dirty_flag))
203         call0 (Qrecompute_lucid_menubar);
204       safe_run_hooks (Qmenu_bar_update_hook);
205       fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
207       /* Now ready to go */
208       items = FRAME_MENU_BAR_ITEMS (f);
210       /* Save the frame's previous menu bar contents data */
211       if (previous_menu_items_used)
212         memcpy (previous_items, aref_addr (f->menu_bar_vector, 0),
213                 previous_menu_items_used * sizeof (Lisp_Object));
215       /* parse stage 1: extract from lisp */
216       save_menu_items ();
218       menu_items = f->menu_bar_vector;
219       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
220       submenu_start = alloca (ASIZE (items) * sizeof *submenu_start);
221       submenu_end = alloca (ASIZE (items) * sizeof *submenu_end);
222       submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes);
223       submenu_top_level_items = alloca (ASIZE (items)
224                                         * sizeof *submenu_top_level_items);
225       init_menu_items ();
226       for (i = 0; i < ASIZE (items); i += 4)
227         {
228           Lisp_Object key, string, maps;
230           key = AREF (items, i);
231           string = AREF (items, i + 1);
232           maps = AREF (items, i + 2);
233           if (NILP (string))
234             break;
236           /* FIXME: we'd like to only parse the needed submenu, but this
237                was causing crashes in the _common parsing code.. need to make
238                sure proper initialization done.. */
239 /*        if (submenu && strcmp ([[submenu title] UTF8String], SSDATA (string)))
240              continue; */
242           submenu_start[i] = menu_items_used;
244           menu_items_n_panes = 0;
245           submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
246           submenu_n_panes[i] = menu_items_n_panes;
247           submenu_end[i] = menu_items_used;
248           n++;
249         }
251       finish_menu_items ();
252       waiting_for_input = owfi;
255       if (submenu && n == 0)
256         {
257           /* should have found a menu for this one but didn't */
258           fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
259                   [[submenu title] UTF8String]);
260           discard_menu_items ();
261           unbind_to (specpdl_count, Qnil);
262           [pool release];
263           unblock_input ();
264           return;
265         }
267       /* parse stage 2: insert into lucid 'widget_value' structures
268          [comments in other terms say not to evaluate lisp code here] */
269       wv = make_widget_value ("menubar", NULL, true, Qnil);
270       wv->button_type = BUTTON_TYPE_NONE;
271       first_wv = wv;
273       for (i = 0; i < 4*n; i += 4)
274         {
275           menu_items_n_panes = submenu_n_panes[i];
276           wv = digest_single_submenu (submenu_start[i], submenu_end[i],
277                                       submenu_top_level_items[i]);
278           if (prev_wv)
279             prev_wv->next = wv;
280           else
281             first_wv->contents = wv;
282           /* Don't set wv->name here; GC during the loop might relocate it.  */
283           wv->enabled = 1;
284           wv->button_type = BUTTON_TYPE_NONE;
285           prev_wv = wv;
286         }
288       set_buffer_internal_1 (prev);
290       /* Compare the new menu items with previous, and leave off if no change */
291       /* FIXME: following other terms here, but seems like this should be
292            done before parse stage 2 above, since its results aren't used */
293       if (previous_menu_items_used
294           && (!submenu || (submenu && submenu == last_submenu))
295           && menu_items_used == previous_menu_items_used)
296         {
297           for (i = 0; i < previous_menu_items_used; i++)
298             /* FIXME: this ALWAYS fails on Buffers menu items.. something
299                  about their strings causes them to change every time, so we
300                  double-check failures */
301             if (!EQ (previous_items[i], AREF (menu_items, i)))
302               if (!(STRINGP (previous_items[i])
303                     && STRINGP (AREF (menu_items, i))
304                     && !strcmp (SSDATA (previous_items[i]),
305                                 SSDATA (AREF (menu_items, i)))))
306                   break;
307           if (i == previous_menu_items_used)
308             {
309               /* No change.. */
311 #if NSMENUPROFILE
312               ftime (&tb);
313               t += 1000*tb.time+tb.millitm;
314               fprintf (stderr, "NO CHANGE!  CUTTING OUT after %ld msec.\n", t);
315 #endif
317               free_menubar_widget_value_tree (first_wv);
318               discard_menu_items ();
319               unbind_to (specpdl_count, Qnil);
320               [pool release];
321               unblock_input ();
322               return;
323             }
324         }
325       /* The menu items are different, so store them in the frame */
326       /* FIXME: this is not correct for single-submenu case */
327       fset_menu_bar_vector (f, menu_items);
328       f->menu_bar_items_used = menu_items_used;
330       /* Calls restore_menu_items, etc., as they were outside */
331       unbind_to (specpdl_count, Qnil);
333       /* Parse stage 2a: now GC cannot happen during the lifetime of the
334          widget_value, so it's safe to store data from a Lisp_String */
335       wv = first_wv->contents;
336       for (i = 0; i < ASIZE (items); i += 4)
337         {
338           Lisp_Object string;
339           string = AREF (items, i + 1);
340           if (NILP (string))
341             break;
343           wv->name = SSDATA (string);
344           update_submenu_strings (wv->contents);
345           wv = wv->next;
346         }
348       /* Now, update the NS menu; if we have a submenu, use that, otherwise
349          create a new menu for each sub and fill it. */
350       if (submenu)
351         {
352           const char *submenuTitle = [[submenu title] UTF8String];
353           for (wv = first_wv->contents; wv; wv = wv->next)
354             {
355               if (!strcmp (submenuTitle, wv->name))
356                 {
357                   [submenu fillWithWidgetValue: wv->contents];
358                   last_submenu = submenu;
359                   break;
360                 }
361             }
362         }
363       else
364         {
365           [menu fillWithWidgetValue: first_wv->contents frame: f];
366         }
368     }
369   else
370     {
371       static int n_previous_strings = 0;
372       static char previous_strings[100][10];
373       static struct frame *last_f = NULL;
374       int n;
375       Lisp_Object string;
377       wv = make_widget_value ("menubar", NULL, true, Qnil);
378       wv->button_type = BUTTON_TYPE_NONE;
379       first_wv = wv;
381       /* Make widget-value tree w/ just the top level menu bar strings */
382       items = FRAME_MENU_BAR_ITEMS (f);
383       if (NILP (items))
384         {
385           free_menubar_widget_value_tree (first_wv);
386           [pool release];
387           unblock_input ();
388           return;
389         }
392       /* check if no change.. this mechanism is a bit rough, but ready */
393       n = ASIZE (items) / 4;
394       if (f == last_f && n_previous_strings == n)
395         {
396           for (i = 0; i<n; i++)
397             {
398               string = AREF (items, 4*i+1);
400               if (EQ (string, make_number (0))) // FIXME: Why???  --Stef
401                 continue;
402               if (NILP (string))
403                 {
404                   if (previous_strings[i][0])
405                     break;
406                   else
407                     continue;
408                 }
409               else if (memcmp (previous_strings[i], SDATA (string),
410                           min (10, SBYTES (string) + 1)))
411                 break;
412             }
414           if (i == n)
415             {
416               free_menubar_widget_value_tree (first_wv);
417               [pool release];
418               unblock_input ();
419               return;
420             }
421         }
423       [menu clear];
424       for (i = 0; i < ASIZE (items); i += 4)
425         {
426           string = AREF (items, i + 1);
427           if (NILP (string))
428             break;
430           if (n < 100)
431             memcpy (previous_strings[i/4], SDATA (string),
432                     min (10, SBYTES (string) + 1));
434           wv = make_widget_value (SSDATA (string), NULL, true, Qnil);
435           wv->button_type = BUTTON_TYPE_NONE;
436           wv->call_data = (void *) (intptr_t) (-1);
438 #ifdef NS_IMPL_COCOA
439           /* we'll update the real copy under app menu when time comes */
440           if (!strcmp ("Services", wv->name))
441             {
442               /* but we need to make sure it will update on demand */
443               [svcsMenu setFrame: f];
444             }
445           else
446 #endif
447           [menu addSubmenuWithTitle: wv->name forFrame: f];
449           if (prev_wv)
450             prev_wv->next = wv;
451           else
452             first_wv->contents = wv;
453           prev_wv = wv;
454         }
456       last_f = f;
457       if (n < 100)
458         n_previous_strings = n;
459       else
460         n_previous_strings = 0;
462     }
463   free_menubar_widget_value_tree (first_wv);
466 #if NSMENUPROFILE
467   ftime (&tb);
468   t += 1000*tb.time+tb.millitm;
469   fprintf (stderr, "Menu update took %ld msec.\n", t);
470 #endif
472   /* set main menu */
473   if (needsSet)
474     [NSApp setMainMenu: menu];
476   [pool release];
477   unblock_input ();
482 /* Main emacs core entry point for menubar menus: called to indicate that the
483    frame's menus have changed, and the *step representation should be updated
484    from Lisp. */
485 void
486 set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
488   ns_update_menubar (f, deep_p, nil);
491 void
492 x_activate_menubar (struct frame *f)
494 #ifdef NS_IMPL_COCOA
495   ns_update_menubar (f, true, nil);
496   ns_check_pending_open_menu ();
497 #endif
503 /* ==========================================================================
505     Menu: class implementation
507    ========================================================================== */
510 /* Menu that can define itself from Emacs "widget_value"s and will lazily
511    update itself when user clicked.  Based on Carbon/AppKit implementation
512    by Yamamoto Mitsuharu. */
513 @implementation EmacsMenu
515 /* override designated initializer */
516 - initWithTitle: (NSString *)title
518   frame = 0;
519   if ((self = [super initWithTitle: title]))
520     [self setAutoenablesItems: NO];
521   return self;
525 /* used for top-level */
526 - initWithTitle: (NSString *)title frame: (struct frame *)f
528   [self initWithTitle: title];
529   frame = f;
530 #ifdef NS_IMPL_COCOA
531   [self setDelegate: self];
532 #endif
533   return self;
537 - (void)setFrame: (struct frame *)f
539   frame = f;
542 #ifdef NS_IMPL_COCOA
543 -(void)trackingNotification:(NSNotification *)notification
545   /* Update menu in menuNeedsUpdate only while tracking menus.  */
546   trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
547                   ? 1 : 0);
548   if (! trackingMenu) ns_check_menu_open (nil);
551 - (void)menuWillOpen:(NSMenu *)menu
553   ++trackingMenu;
555 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
556   // On 10.6 we get repeated calls, only the one for NSSystemDefined is "real".
557   if ([[NSApp currentEvent] type] != NSSystemDefined) return;
558 #endif
560   /* When dragging from one menu to another, we get willOpen followed by didClose,
561      i.e. trackingMenu == 3 in willOpen and then 2 after didClose.
562      We have updated all menus, so avoid doing it when trackingMenu == 3.  */
563   if (trackingMenu == 2)
564     ns_check_menu_open (menu);
567 - (void)menuDidClose:(NSMenu *)menu
569   --trackingMenu;
572 #endif /* NS_IMPL_COCOA */
574 /* delegate method called when a submenu is being opened: run a 'deep' call
575    to set_frame_menubar */
576 - (void)menuNeedsUpdate: (NSMenu *)menu
578   if (!FRAME_LIVE_P (frame))
579     return;
581   /* Cocoa/Carbon will request update on every keystroke
582      via IsMenuKeyEvent -> CheckMenusForKeyEvent.  These are not needed
583      since key equivalents are handled through emacs.
584      On Leopard, even keystroke events generate SystemDefined event.
585      Third-party applications that enhance mouse / trackpad
586      interaction, or also VNC/Remote Desktop will send events
587      of type AppDefined rather than SysDefined.
588      Menus will fail to show up if they haven't been initialized.
589      AppDefined events may lack timing data.
591      Thus, we rely on the didBeginTrackingNotification notification
592      as above to indicate the need for updates.
593      From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
594      key press case, NSMenuPropertyItemImage (e.g.) won't be set.
595   */
596   if (trackingMenu == 0)
597     return;
598 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
599 #ifdef NS_IMPL_GNUSTEP
600   /* Don't know how to do this for anything other than OSX >= 10.5
601      This is wrong, as it might run Lisp code in the event loop.  */
602   ns_update_menubar (frame, true, self);
603 #endif
607 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
609   if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
610       && FRAME_NS_VIEW (SELECTED_FRAME ()))
611     [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
612   return YES;
616 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
617    into an accelerator string.  We are only able to display a single character
618    for an accelerator, together with an optional modifier combination.  (Under
619    Carbon more control was possible, but in Cocoa multi-char strings passed to
620    NSMenuItem get ignored.  For now we try to display a super-single letter
621    combo, and return the others as strings to be appended to the item title.
622    (This is signaled by setting keyEquivModMask to 0 for now.) */
623 -(NSString *)parseKeyEquiv: (const char *)key
625   const char *tpos = key;
626   keyEquivModMask = NSCommandKeyMask;
628   if (!key || !strlen (key))
629     return @"";
631   while (*tpos == ' ' || *tpos == '(')
632     tpos++;
633   if ((*tpos == 's') && (*(tpos+1) == '-'))
634     {
635       return [NSString stringWithFormat: @"%c", tpos[2]];
636     }
637   keyEquivModMask = 0; /* signal */
638   return [NSString stringWithUTF8String: tpos];
642 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
644   NSMenuItem *item;
645   widget_value *wv = (widget_value *)wvptr;
647   if (menu_separator_name_p (wv->name))
648     {
649       item = [NSMenuItem separatorItem];
650       [self addItem: item];
651     }
652   else
653     {
654       NSString *title, *keyEq;
655       title = [NSString stringWithUTF8String: wv->name];
656       if (title == nil)
657         title = @"< ? >";  /* (get out in the open so we know about it) */
659       keyEq = [self parseKeyEquiv: wv->key];
660 #ifdef NS_IMPL_COCOA
661       /* OS X just ignores modifier strings longer than one character */
662       if (keyEquivModMask == 0)
663         title = [title stringByAppendingFormat: @" (%@)", keyEq];
664 #endif
666       item = [self addItemWithTitle: (NSString *)title
667                              action: @selector (menuDown:)
668                       keyEquivalent: keyEq];
669       [item setKeyEquivalentModifierMask: keyEquivModMask];
671       [item setEnabled: wv->enabled];
673       /* Draw radio buttons and tickboxes */
674       if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
675                            wv->button_type == BUTTON_TYPE_RADIO))
676         [item setState: NSOnState];
677       else
678         [item setState: NSOffState];
680       [item setTag: (NSInteger)wv->call_data];
681     }
683   return item;
687 /* convenience */
688 -(void)clear
690   int n;
692   for (n = [self numberOfItems]-1; n >= 0; n--)
693     {
694       NSMenuItem *item = [self itemAtIndex: n];
695       NSString *title = [item title];
696       if ([ns_app_name isEqualToString: title]
697           && ![item isSeparatorItem])
698         continue;
699       [self removeItemAtIndex: n];
700     }
704 - (void)fillWithWidgetValue: (void *)wvptr
706   [self fillWithWidgetValue: wvptr frame: (struct frame *)nil];
709 - (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f
711   widget_value *wv = (widget_value *)wvptr;
713   /* clear existing contents */
714   [self setMenuChangedMessagesEnabled: NO];
715   [self clear];
717   /* add new contents */
718   for (; wv != NULL; wv = wv->next)
719     {
720       NSMenuItem *item = [self addItemWithWidgetValue: wv];
722       if (wv->contents)
723         {
724           EmacsMenu *submenu;
726           if (f)
727             submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f];
728           else
729             submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
731           [self setSubmenu: submenu forItem: item];
732           [submenu fillWithWidgetValue: wv->contents];
733           [submenu release];
734           [item setAction: (SEL)nil];
735         }
736     }
738   [self setMenuChangedMessagesEnabled: YES];
739 #ifdef NS_IMPL_GNUSTEP
740   if ([[self window] isVisible])
741     [self sizeToFit];
742 #endif
746 /* adds an empty submenu and returns it */
747 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
749   NSString *titleStr = [NSString stringWithUTF8String: title];
750   NSMenuItem *item = [self addItemWithTitle: titleStr
751                                      action: (SEL)nil /*@selector (menuDown:) */
752                               keyEquivalent: @""];
753   EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
754   [self setSubmenu: submenu forItem: item];
755   [submenu release];
756   return submenu;
759 /* run a menu in popup mode */
760 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
761                  keymaps: (bool)keymaps
763   EmacsView *view = FRAME_NS_VIEW (f);
764   NSEvent *e, *event;
765   long retVal;
767 /*   p = [view convertPoint:p fromView: nil]; */
768   p.y = NSHeight ([view frame]) - p.y;
769   e = [[view window] currentEvent];
770    event = [NSEvent mouseEventWithType: NSRightMouseDown
771                               location: p
772                          modifierFlags: 0
773                              timestamp: [e timestamp]
774                           windowNumber: [[view window] windowNumber]
775                                context: [e context]
776                            eventNumber: 0/*[e eventNumber] */
777                             clickCount: 1
778                               pressure: 0];
780   context_menu_value = -1;
781   [NSMenu popUpContextMenu: self withEvent: event forView: view];
782   retVal = context_menu_value;
783   context_menu_value = 0;
784   return retVal > 0
785       ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
786       : Qnil;
789 @end  /* EmacsMenu */
793 /* ==========================================================================
795     Context Menu: implementing functions
797    ========================================================================== */
799 Lisp_Object
800 ns_menu_show (struct frame *f, int x, int y, int menuflags,
801               Lisp_Object title, const char **error)
803   EmacsMenu *pmenu;
804   NSPoint p;
805   Lisp_Object tem;
806   ptrdiff_t specpdl_count = SPECPDL_INDEX ();
807   widget_value *wv, *first_wv = 0;
808   bool keymaps = (menuflags & MENU_KEYMAPS);
810   block_input ();
812   p.x = x; p.y = y;
814   /* now parse stage 2 as in ns_update_menubar */
815   wv = make_widget_value ("contextmenu", NULL, true, Qnil);
816   wv->button_type = BUTTON_TYPE_NONE;
817   first_wv = wv;
819 #if 0
820   /* FIXME: a couple of one-line differences prevent reuse */
821   wv = digest_single_submenu (0, menu_items_used, 0);
822 #else
823   {
824   widget_value *save_wv = 0, *prev_wv = 0;
825   widget_value **submenu_stack
826     = alloca (menu_items_used * sizeof *submenu_stack);
827 /*   Lisp_Object *subprefix_stack
828        = alloca (menu_items_used * sizeof *subprefix_stack); */
829   int submenu_depth = 0;
830   int first_pane = 1;
831   int i;
833   /* Loop over all panes and items, filling in the tree.  */
834   i = 0;
835   while (i < menu_items_used)
836     {
837       if (EQ (AREF (menu_items, i), Qnil))
838         {
839           submenu_stack[submenu_depth++] = save_wv;
840           save_wv = prev_wv;
841           prev_wv = 0;
842           first_pane = 1;
843           i++;
844         }
845       else if (EQ (AREF (menu_items, i), Qlambda))
846         {
847           prev_wv = save_wv;
848           save_wv = submenu_stack[--submenu_depth];
849           first_pane = 0;
850           i++;
851         }
852       else if (EQ (AREF (menu_items, i), Qt)
853                && submenu_depth != 0)
854         i += MENU_ITEMS_PANE_LENGTH;
855       /* Ignore a nil in the item list.
856          It's meaningful only for dialog boxes.  */
857       else if (EQ (AREF (menu_items, i), Qquote))
858         i += 1;
859       else if (EQ (AREF (menu_items, i), Qt))
860         {
861           /* Create a new pane.  */
862           Lisp_Object pane_name, prefix;
863           const char *pane_string;
865           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
866           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
868 #ifndef HAVE_MULTILINGUAL_MENU
869           if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
870             {
871               pane_name = ENCODE_MENU_STRING (pane_name);
872               ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
873             }
874 #endif
875           pane_string = (NILP (pane_name)
876                          ? "" : SSDATA (pane_name));
877           /* If there is just one top-level pane, put all its items directly
878              under the top-level menu.  */
879           if (menu_items_n_panes == 1)
880             pane_string = "";
882           /* If the pane has a meaningful name,
883              make the pane a top-level menu item
884              with its items as a submenu beneath it.  */
885           if (!keymaps && strcmp (pane_string, ""))
886             {
887               wv = make_widget_value (pane_string, NULL, true, Qnil);
888               if (save_wv)
889                 save_wv->next = wv;
890               else
891                 first_wv->contents = wv;
892               if (keymaps && !NILP (prefix))
893                 wv->name++;
894               wv->button_type = BUTTON_TYPE_NONE;
895               save_wv = wv;
896               prev_wv = 0;
897             }
898           else if (first_pane)
899             {
900               save_wv = wv;
901               prev_wv = 0;
902             }
903           first_pane = 0;
904           i += MENU_ITEMS_PANE_LENGTH;
905         }
906       else
907         {
908           /* Create a new item within current pane.  */
909           Lisp_Object item_name, enable, descrip, def, type, selected, help;
910           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
911           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
912           descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
913           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
914           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
915           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
916           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
918 #ifndef HAVE_MULTILINGUAL_MENU
919           if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
920             {
921               item_name = ENCODE_MENU_STRING (item_name);
922               ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
923             }
925           if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
926             {
927               descrip = ENCODE_MENU_STRING (descrip);
928               ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
929             }
930 #endif /* not HAVE_MULTILINGUAL_MENU */
932           wv = make_widget_value (SSDATA (item_name), NULL, !NILP (enable),
933                                   STRINGP (help) ? help : Qnil);
934           if (prev_wv)
935             prev_wv->next = wv;
936           else
937             save_wv->contents = wv;
938           if (!NILP (descrip))
939             wv->key = SSDATA (descrip);
940           /* If this item has a null value,
941              make the call_data null so that it won't display a box
942              when the mouse is on it.  */
943           wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
945           if (NILP (type))
946             wv->button_type = BUTTON_TYPE_NONE;
947           else if (EQ (type, QCtoggle))
948             wv->button_type = BUTTON_TYPE_TOGGLE;
949           else if (EQ (type, QCradio))
950             wv->button_type = BUTTON_TYPE_RADIO;
951           else
952             emacs_abort ();
954           wv->selected = !NILP (selected);
956           prev_wv = wv;
958           i += MENU_ITEMS_ITEM_LENGTH;
959         }
960     }
961   }
962 #endif
964   if (!NILP (title))
965     {
966       widget_value *wv_title;
967       widget_value *wv_sep = make_widget_value ("--", NULL, false, Qnil);
969       /* Maybe replace this separator with a bitmap or owner-draw item
970          so that it looks better.  Having two separators looks odd.  */
971       wv_sep->next = first_wv->contents;
973 #ifndef HAVE_MULTILINGUAL_MENU
974       if (STRING_MULTIBYTE (title))
975         title = ENCODE_MENU_STRING (title);
976 #endif
977       wv_title = make_widget_value (SSDATA (title), NULL, false, Qnil);
978       wv_title->button_type = BUTTON_TYPE_NONE;
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   unblock_input ();
995   return tem;
999 /* ==========================================================================
1001     Toolbar: externally-called functions
1003    ========================================================================== */
1005 void
1006 free_frame_tool_bar (struct frame *f)
1007 /* --------------------------------------------------------------------------
1008     Under NS we just hide the toolbar until it might be needed again.
1009    -------------------------------------------------------------------------- */
1011   EmacsView *view = FRAME_NS_VIEW (f);
1012   block_input ();
1013   view->wait_for_tool_bar = NO;
1014   [[view toolbar] setVisible: NO];
1015   FRAME_TOOLBAR_HEIGHT (f) = 0;
1016   unblock_input ();
1019 void
1020 update_frame_tool_bar (struct frame *f)
1021 /* --------------------------------------------------------------------------
1022     Update toolbar contents
1023    -------------------------------------------------------------------------- */
1025   int i, k = 0;
1026   EmacsView *view = FRAME_NS_VIEW (f);
1027   NSWindow *window = [view window];
1028   EmacsToolbar *toolbar = [view toolbar];
1029   int oldh;
1031   if (view == nil || toolbar == nil) return;
1032   block_input ();
1034   oldh = FRAME_TOOLBAR_HEIGHT (f);
1036 #ifdef NS_IMPL_COCOA
1037   [toolbar clearActive];
1038 #else
1039   [toolbar clearAll];
1040 #endif
1042   /* update EmacsToolbar as in GtkUtils, build items list */
1043   for (i = 0; i < f->n_tool_bar_items; ++i)
1044     {
1045 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1046                             i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1048       BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1049       int idx;
1050       ptrdiff_t img_id;
1051       struct image *img;
1052       Lisp_Object image;
1053       Lisp_Object helpObj;
1054       const char *helpText;
1056       /* Check if this is a separator.  */
1057       if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1058         {
1059           /* Skip separators.  Newer OSX don't show them, and on GNUstep they
1060              are wide as a button, thus overflowing the toolbar most of
1061              the time.  */
1062           continue;
1063         }
1065       /* If image is a vector, choose the image according to the
1066          button state.  */
1067       image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1068       if (VECTORP (image))
1069         {
1070           /* NS toolbar auto-computes disabled and selected images */
1071           idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1072           eassert (ASIZE (image) >= idx);
1073           image = AREF (image, idx);
1074         }
1075       else
1076         {
1077           idx = -1;
1078         }
1079       helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1080       if (NILP (helpObj))
1081         helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1082       helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1084       /* Ignore invalid image specifications.  */
1085       if (!valid_image_p (image))
1086         {
1087           /* Don't log anything, GNUS makes invalid images all the time.  */
1088           continue;
1089         }
1091       img_id = lookup_image (f, image);
1092       img = IMAGE_FROM_ID (f, img_id);
1093       prepare_image_for_display (f, img);
1095       if (img->load_failed_p || img->pixmap == nil)
1096         {
1097           NSLog (@"Could not prepare toolbar image for display.");
1098           continue;
1099         }
1101       [toolbar addDisplayItemWithImage: img->pixmap
1102                                    idx: k++
1103                                    tag: i
1104                               helpText: helpText
1105                                enabled: enabled_p];
1106 #undef TOOLPROP
1107     }
1109   if (![toolbar isVisible])
1110       [toolbar setVisible: YES];
1112 #ifdef NS_IMPL_COCOA
1113   if ([toolbar changed])
1114     {
1115       /* inform app that toolbar has changed */
1116       NSDictionary *dict = [toolbar configurationDictionary];
1117       NSMutableDictionary *newDict = [dict mutableCopy];
1118       NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1119       id key;
1120       while ((key = [keys nextObject]) != nil)
1121         {
1122           NSObject *val = [dict objectForKey: key];
1123           if ([val isKindOfClass: [NSArray class]])
1124             {
1125               [newDict setObject:
1126                          [toolbar toolbarDefaultItemIdentifiers: toolbar]
1127                           forKey: key];
1128               break;
1129             }
1130         }
1131       [toolbar setConfigurationFromDictionary: newDict];
1132       [newDict release];
1133     }
1134 #endif
1136   FRAME_TOOLBAR_HEIGHT (f) =
1137     NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1138     - FRAME_NS_TITLEBAR_HEIGHT (f);
1139   if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1140     FRAME_TOOLBAR_HEIGHT (f) = 0;
1142   if (oldh != FRAME_TOOLBAR_HEIGHT (f))
1143     [view updateFrameSize:YES];
1144   if (view->wait_for_tool_bar && FRAME_TOOLBAR_HEIGHT (f) > 0)
1145     {
1146       view->wait_for_tool_bar = NO;
1147       [view setNeedsDisplay: YES];
1148     }
1150   unblock_input ();
1154 /* ==========================================================================
1156     Toolbar: class implementation
1158    ========================================================================== */
1160 @implementation EmacsToolbar
1162 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1164   self = [super initWithIdentifier: identifier];
1165   emacsView = view;
1166   [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1167   [self setSizeMode: NSToolbarSizeModeSmall];
1168   [self setDelegate: self];
1169   identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1170   activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1171   prevIdentifiers = nil;
1172   prevEnablement = enablement = 0L;
1173   return self;
1176 - (void)dealloc
1178   [prevIdentifiers release];
1179   [activeIdentifiers release];
1180   [identifierToItem release];
1181   [super dealloc];
1184 - (void) clearActive
1186   [prevIdentifiers release];
1187   prevIdentifiers = [activeIdentifiers copy];
1188   [activeIdentifiers removeAllObjects];
1189   prevEnablement = enablement;
1190   enablement = 0L;
1193 - (void) clearAll
1195   [self clearActive];
1196   while ([[self items] count] > 0)
1197     [self removeItemAtIndex: 0];
1200 - (BOOL) changed
1202   return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1203     enablement == prevEnablement ? NO : YES;
1206 - (void) addDisplayItemWithImage: (EmacsImage *)img
1207                              idx: (int)idx
1208                              tag: (int)tag
1209                         helpText: (const char *)help
1210                          enabled: (BOOL)enabled
1212   /* 1) come up w/identifier */
1213   NSString *identifier
1214     = [NSString stringWithFormat: @"%lu", (unsigned long)[img hash]];
1215   [activeIdentifiers addObject: identifier];
1217   /* 2) create / reuse item */
1218   NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1219   if (item == nil)
1220     {
1221       item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1222                autorelease];
1223       [item setImage: img];
1224       [item setToolTip: [NSString stringWithUTF8String: help]];
1225       [item setTarget: emacsView];
1226       [item setAction: @selector (toolbarClicked:)];
1227       [identifierToItem setObject: item forKey: identifier];
1228     }
1230 #ifdef NS_IMPL_GNUSTEP
1231   [self insertItemWithItemIdentifier: identifier atIndex: idx];
1232 #endif
1234   [item setTag: tag];
1235   [item setEnabled: enabled];
1237   /* 3) update state */
1238   enablement = (enablement << 1) | (enabled == YES);
1241 /* This overrides super's implementation, which automatically sets
1242    all items to enabled state (for some reason). */
1243 - (void)validateVisibleItems
1248 /* delegate methods */
1250 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1251       itemForItemIdentifier: (NSString *)itemIdentifier
1252   willBeInsertedIntoToolbar: (BOOL)flag
1254   /* look up NSToolbarItem by identifier and return... */
1255   return [identifierToItem objectForKey: itemIdentifier];
1258 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1260   /* return entire set.. */
1261   return activeIdentifiers;
1264 /* for configuration palette (not yet supported) */
1265 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1267   /* return entire set... */
1268   return activeIdentifiers;
1269   //return [identifierToItem allKeys];
1272 /* optional and unneeded */
1273 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1274 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1275 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1277 @end  /* EmacsToolbar */
1281 /* ==========================================================================
1283     Tooltip: class implementation
1285    ========================================================================== */
1287 /* Needed because NeXTstep does not provide enough control over tooltip
1288    display. */
1289 @implementation EmacsTooltip
1291 - init
1293   NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1294                                             blue: 0.792 alpha: 0.95];
1295   NSFont *font = [NSFont toolTipsFontOfSize: 0];
1296   NSFont *sfont = [font screenFont];
1297   int height = [sfont ascender] - [sfont descender];
1298 /*[font boundingRectForFont].size.height; */
1299   NSRect r = NSMakeRect (0, 0, 100, height+6);
1301   textField = [[NSTextField alloc] initWithFrame: r];
1302   [textField setFont: font];
1303   [textField setBackgroundColor: col];
1305   [textField setEditable: NO];
1306   [textField setSelectable: NO];
1307   [textField setBordered: NO];
1308   [textField setBezeled: NO];
1309   [textField setDrawsBackground: YES];
1311   win = [[NSWindow alloc]
1312             initWithContentRect: [textField frame]
1313                       styleMask: 0
1314                         backing: NSBackingStoreBuffered
1315                           defer: YES];
1316   [win setHasShadow: YES];
1317   [win setReleasedWhenClosed: NO];
1318   [win setDelegate: self];
1319   [[win contentView] addSubview: textField];
1320 /*  [win setBackgroundColor: col]; */
1321   [win setOpaque: NO];
1323   return self;
1326 - (void) dealloc
1328   [win close];
1329   [win release];
1330   [textField release];
1331   [super dealloc];
1334 - (void) setText: (char *)text
1336   NSString *str = [NSString stringWithUTF8String: text];
1337   NSRect r  = [textField frame];
1338   NSSize tooltipDims;
1340   [textField setStringValue: str];
1341   tooltipDims = [[textField cell] cellSize];
1343   r.size.width = tooltipDims.width;
1344   r.size.height = tooltipDims.height;
1345   [textField setFrame: r];
1348 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1350   NSRect wr = [win frame];
1352   wr.origin = NSMakePoint (x, y);
1353   wr.size = [textField frame].size;
1355   [win setFrame: wr display: YES];
1356   [win setLevel: NSPopUpMenuWindowLevel];
1357   [win orderFront: self];
1358   [win display];
1359   timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1360                                          selector: @selector (hide)
1361                                          userInfo: nil repeats: NO];
1362   [timer retain];
1365 - (void) hide
1367   [win close];
1368   if (timer != nil)
1369     {
1370       if ([timer isValid])
1371         [timer invalidate];
1372       [timer release];
1373       timer = nil;
1374     }
1377 - (BOOL) isActive
1379   return timer != nil;
1382 - (NSRect) frame
1384   return [textField frame];
1387 @end  /* EmacsTooltip */
1391 /* ==========================================================================
1393     Popup Dialog: implementing functions
1395    ========================================================================== */
1397 struct Popdown_data
1399   NSAutoreleasePool *pool;
1400   EmacsDialogPanel *dialog;
1403 static void
1404 pop_down_menu (void *arg)
1406   struct Popdown_data *unwind_data = arg;
1408   block_input ();
1409   if (popup_activated_flag)
1410     {
1411       EmacsDialogPanel *panel = unwind_data->dialog;
1412       popup_activated_flag = 0;
1413       [panel close];
1414       [unwind_data->pool release];
1415       [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1416     }
1418   xfree (unwind_data);
1419   unblock_input ();
1423 Lisp_Object
1424 ns_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
1426   id dialog;
1427   Lisp_Object window, tem, title;
1428   NSPoint p;
1429   BOOL isQ;
1430   NSAutoreleasePool *pool;
1432   NSTRACE (x-popup-dialog);
1434   isQ = NILP (header);
1436   check_window_system (f);
1438   p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1439   p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1441   title = Fcar (contents);
1442   CHECK_STRING (title);
1444   if (NILP (Fcar (Fcdr (contents))))
1445     /* No buttons specified, add an "Ok" button so users can pop down
1446        the dialog.  */
1447     contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1449   block_input ();
1450   pool = [[NSAutoreleasePool alloc] init];
1451   dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1452                                            isQuestion: isQ];
1454   {
1455     ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1456     struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1458     unwind_data->pool = pool;
1459     unwind_data->dialog = dialog;
1461     record_unwind_protect_ptr (pop_down_menu, unwind_data);
1462     popup_activated_flag = 1;
1463     tem = [dialog runDialogAt: p];
1464     unbind_to (specpdl_count, Qnil);  /* calls pop_down_menu */
1465   }
1467   unblock_input ();
1469   return tem;
1473 /* ==========================================================================
1475     Popup Dialog: class implementation
1477    ========================================================================== */
1479 @interface FlippedView : NSView
1482 @end
1484 @implementation FlippedView
1485 - (BOOL)isFlipped
1487   return YES;
1489 @end
1491 @implementation EmacsDialogPanel
1493 #define SPACER          8.0
1494 #define ICONSIZE        64.0
1495 #define TEXTHEIGHT      20.0
1496 #define MINCELLWIDTH    90.0
1498 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1499               backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1501   NSSize spacing = {SPACER, SPACER};
1502   NSRect area;
1503   id cell;
1504   NSImageView *imgView;
1505   FlippedView *contentView;
1506   NSImage *img;
1508   dialog_return   = Qundefined;
1509   button_values   = NULL;
1510   area.origin.x   = 3*SPACER;
1511   area.origin.y   = 2*SPACER;
1512   area.size.width = ICONSIZE;
1513   area.size.height= ICONSIZE;
1514   img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1515   [img setScalesWhenResized: YES];
1516   [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1517   imgView = [[NSImageView alloc] initWithFrame: area];
1518   [imgView setImage: img];
1519   [imgView setEditable: NO];
1520   [img autorelease];
1521   [imgView autorelease];
1523   aStyle = NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask;
1524   flag = YES;
1525   rows = 0;
1526   cols = 1;
1527   [super initWithContentRect: contentRect styleMask: aStyle
1528                      backing: backingType defer: flag];
1529   contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1530   [contentView autorelease];
1532   [self setContentView: contentView];
1534   [[self contentView] setAutoresizesSubviews: YES];
1536   [[self contentView] addSubview: imgView];
1537   [self setTitle: @""];
1539   area.origin.x   += ICONSIZE+2*SPACER;
1540 /*  area.origin.y   = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1541   area.size.width = 400;
1542   area.size.height= TEXTHEIGHT;
1543   command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1544   [[self contentView] addSubview: command];
1545   [command setStringValue: ns_app_name];
1546   [command setDrawsBackground: NO];
1547   [command setBezeled: NO];
1548   [command setSelectable: NO];
1549   [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1551 /*  area.origin.x   = ICONSIZE+2*SPACER;
1552   area.origin.y   = TEXTHEIGHT + 2*SPACER;
1553   area.size.width = 400;
1554   area.size.height= 2;
1555   tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1556   [[self contentView] addSubview: tem];
1557   [tem setTitlePosition: NSNoTitle];
1558   [tem setAutoresizingMask: NSViewWidthSizable];*/
1560 /*  area.origin.x = ICONSIZE+2*SPACER; */
1561   area.origin.y += TEXTHEIGHT+SPACER;
1562   area.size.width = 400;
1563   area.size.height= TEXTHEIGHT;
1564   title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1565   [[self contentView] addSubview: title];
1566   [title setDrawsBackground: NO];
1567   [title setBezeled: NO];
1568   [title setSelectable: NO];
1569   [title setFont: [NSFont systemFontOfSize: 11.0]];
1571   cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1572   [cell setBordered: NO];
1573   [cell setEnabled: NO];
1574   [cell setCellAttribute: NSCellIsInsetButton to: 8];
1575   [cell setBezelStyle: NSRoundedBezelStyle];
1577   matrix = [[NSMatrix alloc] initWithFrame: contentRect
1578                                       mode: NSHighlightModeMatrix
1579                                  prototype: cell
1580                               numberOfRows: 0
1581                            numberOfColumns: 1];
1582   [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1583                                       area.origin.y + (TEXTHEIGHT+3*SPACER))];
1584   [matrix setIntercellSpacing: spacing];
1585   [matrix autorelease];
1587   [[self contentView] addSubview: matrix];
1588   [self setOneShot: YES];
1589   [self setReleasedWhenClosed: YES];
1590   [self setHidesOnDeactivate: YES];
1591   return self;
1595 - (BOOL)windowShouldClose: (id)sender
1597   window_closed = YES;
1598   [NSApp stop:self];
1599   return NO;
1602 - (void)dealloc
1604   xfree (button_values);
1605   [super dealloc];
1608 - (void)process_dialog: (Lisp_Object) list
1610   Lisp_Object item, lst = list;
1611   int row = 0;
1612   int buttons = 0, btnnr = 0;
1614   for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1615     {
1616       item = XCAR (list);
1617       if (XTYPE (item) == Lisp_Cons)
1618         ++buttons;
1619     }
1621   if (buttons > 0)
1622     button_values = xmalloc (buttons * sizeof *button_values);
1624   for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1625     {
1626       item = XCAR (list);
1627       if (XTYPE (item) == Lisp_String)
1628         {
1629           [self addString: SSDATA (item) row: row++];
1630         }
1631       else if (XTYPE (item) == Lisp_Cons)
1632         {
1633           button_values[btnnr] = XCDR (item);
1634           [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1635           ++btnnr;
1636         }
1637       else if (NILP (item))
1638         {
1639           [self addSplit];
1640           row = 0;
1641         }
1642     }
1646 - (void)addButton: (char *)str value: (int)tag row: (int)row
1648   id cell;
1650   if (row >= rows)
1651     {
1652       [matrix addRow];
1653       rows++;
1654     }
1655   cell = [matrix cellAtRow: row column: cols-1];
1656   [cell setTarget: self];
1657   [cell setAction: @selector (clicked: )];
1658   [cell setTitle: [NSString stringWithUTF8String: str]];
1659   [cell setTag: tag];
1660   [cell setBordered: YES];
1661   [cell setEnabled: YES];
1665 - (void)addString: (char *)str row: (int)row
1667   id cell;
1669   if (row >= rows)
1670     {
1671       [matrix addRow];
1672       rows++;
1673     }
1674   cell = [matrix cellAtRow: row column: cols-1];
1675   [cell setTitle: [NSString stringWithUTF8String: str]];
1676   [cell setBordered: YES];
1677   [cell setEnabled: NO];
1681 - (void)addSplit
1683   [matrix addColumn];
1684   cols++;
1688 - (void)clicked: sender
1690   NSArray *sellist = nil;
1691   EMACS_INT seltag;
1693   sellist = [sender selectedCells];
1694   if ([sellist count] < 1)
1695     return;
1697   seltag = [[sellist objectAtIndex: 0] tag];
1698   dialog_return = button_values[seltag];
1699   [NSApp stop:self];
1703 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1705   Lisp_Object head;
1706   [super init];
1708   if (XTYPE (contents) == Lisp_Cons)
1709     {
1710       head = Fcar (contents);
1711       [self process_dialog: Fcdr (contents)];
1712     }
1713   else
1714     head = contents;
1716   if (XTYPE (head) == Lisp_String)
1717       [title setStringValue:
1718                  [NSString stringWithUTF8String: SSDATA (head)]];
1719   else if (isQ == YES)
1720       [title setStringValue: @"Question"];
1721   else
1722       [title setStringValue: @"Information"];
1724   {
1725     int i;
1726     NSRect r, s, t;
1728     if (cols == 1 && rows > 1)  /* Never told where to split */
1729       {
1730         [matrix addColumn];
1731         for (i = 0; i < rows/2; i++)
1732           {
1733             [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1734                       atRow: i column: 1];
1735             [matrix removeRow: (rows+1)/2];
1736           }
1737       }
1739     [matrix sizeToFit];
1740     {
1741       NSSize csize = [matrix cellSize];
1742       if (csize.width < MINCELLWIDTH)
1743         {
1744           csize.width = MINCELLWIDTH;
1745           [matrix setCellSize: csize];
1746           [matrix sizeToCells];
1747         }
1748     }
1750     [title sizeToFit];
1751     [command sizeToFit];
1753     t = [matrix frame];
1754     r = [title frame];
1755     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1756       {
1757         t.origin.x   = r.origin.x;
1758         t.size.width = r.size.width;
1759       }
1760     r = [command frame];
1761     if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1762       {
1763         t.origin.x   = r.origin.x;
1764         t.size.width = r.size.width;
1765       }
1767     r = [self frame];
1768     s = [(NSView *)[self contentView] frame];
1769     r.size.width  += t.origin.x+t.size.width +2*SPACER-s.size.width;
1770     r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1771     [self setFrame: r display: NO];
1772   }
1774   return self;
1779 - (void)timeout_handler: (NSTimer *)timedEntry
1781   NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1782                             location: NSMakePoint (0, 0)
1783                        modifierFlags: 0
1784                            timestamp: 0
1785                         windowNumber: [[NSApp mainWindow] windowNumber]
1786                              context: [NSApp context]
1787                              subtype: 0
1788                                data1: 0
1789                                data2: 0];
1791   timer_fired = YES;
1792   /* We use sto because stopModal/abortModal out of the main loop does not
1793      seem to work in 10.6.  But as we use stop we must send a real event so
1794      the stop is seen and acted upon.  */
1795   [NSApp stop:self];
1796   [NSApp postEvent: nxev atStart: NO];
1799 - (Lisp_Object)runDialogAt: (NSPoint)p
1801   Lisp_Object ret = Qundefined;
1803   while (popup_activated_flag)
1804     {
1805       NSTimer *tmo = nil;
1806       struct timespec next_time = timer_check ();
1808       if (timespec_valid_p (next_time))
1809         {
1810           double time = timespectod (next_time);
1811           tmo = [NSTimer timerWithTimeInterval: time
1812                                         target: self
1813                                       selector: @selector (timeout_handler:)
1814                                       userInfo: 0
1815                                        repeats: NO];
1816           [[NSRunLoop currentRunLoop] addTimer: tmo
1817                                        forMode: NSModalPanelRunLoopMode];
1818         }
1819       timer_fired = NO;
1820       dialog_return = Qundefined;
1821       [NSApp runModalForWindow: self];
1822       ret = dialog_return;
1823       if (! timer_fired)
1824         {
1825           if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1826           break;
1827         }
1828     }
1830   if (EQ (ret, Qundefined) && window_closed)
1831     /* Make close button pressed equivalent to C-g.  */
1832     Fsignal (Qquit, Qnil);
1834   return ret;
1837 @end
1840 /* ==========================================================================
1842     Lisp definitions
1844    ========================================================================== */
1846 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1847        doc: /* Cause the NS menu to be re-calculated.  */)
1848      (void)
1850   set_frame_menubar (SELECTED_FRAME (), 1, 0);
1851   return Qnil;
1855 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1856        doc: /* Return t if a menu or popup dialog is active.  */)
1857      (void)
1859   return popup_activated () ? Qt : Qnil;
1862 /* ==========================================================================
1864     Lisp interface declaration
1866    ========================================================================== */
1868 void
1869 syms_of_nsmenu (void)
1871 #ifndef NS_IMPL_COCOA
1872   /* Don't know how to keep track of this in Next/Open/GNUstep.  Always
1873      update menus there.  */
1874   trackingMenu = 1;
1875 #endif
1876   defsubr (&Sns_reset_menu);
1877   defsubr (&Smenu_or_popup_active_p);
1879   Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1880   staticpro (&Qdebug_on_next_call);