1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2 Copyright (C) 2007, 2008, 2009, 2010, 2011 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. */
35 #include "blockinput.h"
37 #include "termhooks.h"
41 #define NSMENUPROFILE 0
44 #include <sys/timeb.h>
45 #include <sys/types.h>
48 #define MenuStagger 10.0
51 int menu_trace_num = 0;
52 #define NSTRACE(x) fprintf (stderr, "%s:%d: [%d] " #x "\n", \
53 __FILE__, __LINE__, ++menu_trace_num)
59 /* Include lisp -> C common menu parsing code */
60 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
61 #include "nsmenu_common.c"
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 Voverriding_local_map, Voverriding_local_map_menu_flag,
69 Qoverriding_local_map, Qoverriding_terminal_local_map;
71 extern long context_menu_value;
72 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
74 /* Nonzero means a menu is currently active. */
75 static int popup_activated_flag;
76 static NSModalSession popupSession;
78 /* NOTE: toolbar implementation is at end,
79 following complete menu implementation. */
82 /* ==========================================================================
84 Menu: Externally-called functions
86 ========================================================================== */
89 /* FIXME: not currently used, but should normalize with other terms. */
91 x_activate_menubar (struct frame *f)
93 fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
97 /* Supposed to discard menubar and free storage. Since we share the
98 menubar among frames and update its context for the focused window,
99 there is nothing to do here. */
101 free_frame_menubar (struct frame *f)
108 popup_activated (void)
110 return popup_activated_flag;
114 /* --------------------------------------------------------------------------
115 Update menubar. Three cases:
116 1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
117 just top-level menu strings (OS X), or goto case (2) (GNUstep).
118 2) deep_p = 1, submenu = nil: Recompute all submenus.
119 3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
120 -------------------------------------------------------------------------- */
122 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
124 NSAutoreleasePool *pool;
125 id menu = [NSApp mainMenu];
126 static EmacsMenu *last_submenu = nil;
128 const char *submenuTitle = [[submenu title] UTF8String];
129 extern int waiting_for_input;
132 widget_value *wv, *first_wv, *prev_wv = 0;
140 NSTRACE (set_frame_menubar);
142 if (f != SELECTED_FRAME ())
144 XSETFRAME (Vmenu_updating_frame, f);
145 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
148 pool = [[NSAutoreleasePool alloc] init];
150 /* Menu may have been created automatically; if so, discard it. */
151 if ([menu isKindOfClass: [EmacsMenu class]] == NO)
159 menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
163 { /* close up anything on there */
164 id attMenu = [menu attachedMenu];
171 t = -(1000*tb.time+tb.millitm);
174 #ifdef NS_IMPL_GNUSTEP
175 deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
180 /* Fully parse one or more of the submenus. */
182 int *submenu_start, *submenu_end;
183 int *submenu_top_level_items, *submenu_n_panes;
184 struct buffer *prev = current_buffer;
186 int specpdl_count = SPECPDL_INDEX ();
187 int previous_menu_items_used = f->menu_bar_items_used;
188 Lisp_Object *previous_items
189 = (Lisp_Object *) alloca (previous_menu_items_used
190 * sizeof (Lisp_Object));
192 /* lisp preliminaries */
193 buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
194 specbind (Qinhibit_quit, Qt);
195 specbind (Qdebug_on_next_call, Qnil);
196 record_unwind_save_match_data ();
197 if (NILP (Voverriding_local_map_menu_flag))
199 specbind (Qoverriding_terminal_local_map, Qnil);
200 specbind (Qoverriding_local_map, Qnil);
202 set_buffer_internal_1 (XBUFFER (buffer));
204 /* TODO: for some reason this is not needed in other terms,
205 but some menu updates call Info-extract-pointer which causes
206 abort-on-error if waiting-for-input. Needs further investigation. */
207 owfi = waiting_for_input;
208 waiting_for_input = 0;
210 /* lucid hook and possible reset */
211 safe_run_hooks (Qactivate_menubar_hook);
212 if (! NILP (Vlucid_menu_bar_dirty_flag))
213 call0 (Qrecompute_lucid_menubar);
214 safe_run_hooks (Qmenu_bar_update_hook);
215 FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
217 /* Now ready to go */
218 items = FRAME_MENU_BAR_ITEMS (f);
220 /* Save the frame's previous menu bar contents data */
221 if (previous_menu_items_used)
222 memcpy (previous_items, XVECTOR (f->menu_bar_vector)->contents,
223 previous_menu_items_used * sizeof (Lisp_Object));
225 /* parse stage 1: extract from lisp */
228 menu_items = f->menu_bar_vector;
229 menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
230 submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
231 submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
232 submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
233 submenu_top_level_items
234 = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
236 for (i = 0; i < XVECTOR (items)->size; i += 4)
238 Lisp_Object key, string, maps;
240 key = XVECTOR (items)->contents[i];
241 string = XVECTOR (items)->contents[i + 1];
242 maps = XVECTOR (items)->contents[i + 2];
246 /* FIXME: we'd like to only parse the needed submenu, but this
247 was causing crashes in the _common parsing code.. need to make
248 sure proper initialization done.. */
249 /* if (submenu && strcmp (submenuTitle, SDATA (string)))
252 submenu_start[i] = menu_items_used;
254 menu_items_n_panes = 0;
255 submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
256 submenu_n_panes[i] = menu_items_n_panes;
257 submenu_end[i] = menu_items_used;
261 finish_menu_items ();
262 waiting_for_input = owfi;
265 if (submenu && n == 0)
267 /* should have found a menu for this one but didn't */
268 fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
270 discard_menu_items ();
271 unbind_to (specpdl_count, Qnil);
277 /* parse stage 2: insert into lucid 'widget_value' structures
278 [comments in other terms say not to evaluate lisp code here] */
279 wv = xmalloc_widget_value ();
280 wv->name = "menubar";
283 wv->button_type = BUTTON_TYPE_NONE;
287 for (i = 0; i < 4*n; i += 4)
289 menu_items_n_panes = submenu_n_panes[i];
290 wv = digest_single_submenu (submenu_start[i], submenu_end[i],
291 submenu_top_level_items[i]);
295 first_wv->contents = wv;
296 /* Don't set wv->name here; GC during the loop might relocate it. */
298 wv->button_type = BUTTON_TYPE_NONE;
302 set_buffer_internal_1 (prev);
304 /* Compare the new menu items with previous, and leave off if no change */
305 /* FIXME: following other terms here, but seems like this should be
306 done before parse stage 2 above, since its results aren't used */
307 if (previous_menu_items_used
308 && (!submenu || (submenu && submenu == last_submenu))
309 && menu_items_used == previous_menu_items_used)
311 for (i = 0; i < previous_menu_items_used; i++)
312 /* FIXME: this ALWAYS fails on Buffers menu items.. something
313 about their strings causes them to change every time, so we
314 double-check failures */
315 if (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i]))
316 if (!(STRINGP (previous_items[i])
317 && STRINGP (XVECTOR (menu_items)->contents[i])
318 && !strcmp (SDATA (previous_items[i]),
319 SDATA (XVECTOR (menu_items)->contents[i]))))
321 if (i == previous_menu_items_used)
327 t += 1000*tb.time+tb.millitm;
328 fprintf (stderr, "NO CHANGE! CUTTING OUT after %ld msec.\n", t);
331 free_menubar_widget_value_tree (first_wv);
332 discard_menu_items ();
333 unbind_to (specpdl_count, Qnil);
339 /* The menu items are different, so store them in the frame */
340 /* FIXME: this is not correct for single-submenu case */
341 f->menu_bar_vector = menu_items;
342 f->menu_bar_items_used = menu_items_used;
344 /* Calls restore_menu_items, etc., as they were outside */
345 unbind_to (specpdl_count, Qnil);
347 /* Parse stage 2a: now GC cannot happen during the lifetime of the
348 widget_value, so it's safe to store data from a Lisp_String */
349 wv = first_wv->contents;
350 for (i = 0; i < XVECTOR (items)->size; i += 4)
353 string = XVECTOR (items)->contents[i + 1];
356 /* if (submenu && strcmp (submenuTitle, SDATA (string)))
359 wv->name = (char *) SDATA (string);
360 update_submenu_strings (wv->contents);
364 /* Now, update the NS menu; if we have a submenu, use that, otherwise
365 create a new menu for each sub and fill it. */
368 for (wv = first_wv->contents; wv; wv = wv->next)
370 if (!strcmp (submenuTitle, wv->name))
372 [submenu fillWithWidgetValue: wv->contents];
373 last_submenu = submenu;
380 [menu fillWithWidgetValue: first_wv->contents];
386 static int n_previous_strings = 0;
387 static char previous_strings[100][10];
388 static struct frame *last_f = NULL;
392 wv = xmalloc_widget_value ();
393 wv->name = "menubar";
396 wv->button_type = BUTTON_TYPE_NONE;
400 /* Make widget-value tree w/ just the top level menu bar strings */
401 items = FRAME_MENU_BAR_ITEMS (f);
410 /* check if no change.. this mechanism is a bit rough, but ready */
411 n = XVECTOR (items)->size / 4;
412 if (f == last_f && n_previous_strings == n)
414 for (i = 0; i<n; i++)
416 string = AREF (items, 4*i+1);
418 if (EQ (string, make_number (0))) // FIXME: Why??? --Stef
421 if (previous_strings[i][0])
425 if (strncmp (previous_strings[i], SDATA (string), 10))
438 for (i = 0; i < XVECTOR (items)->size; i += 4)
440 string = XVECTOR (items)->contents[i + 1];
445 strncpy (previous_strings[i/4], SDATA (string), 10);
447 wv = xmalloc_widget_value ();
448 wv->name = (char *) SDATA (string);
451 wv->button_type = BUTTON_TYPE_NONE;
453 wv->call_data = (void *) (EMACS_INT) (-1);
456 /* we'll update the real copy under app menu when time comes */
457 if (!strcmp ("Services", wv->name))
459 /* but we need to make sure it will update on demand */
460 [svcsMenu setFrame: f];
461 [svcsMenu setDelegate: svcsMenu];
465 [menu addSubmenuWithTitle: wv->name forFrame: f];
470 first_wv->contents = wv;
476 n_previous_strings = n;
478 n_previous_strings = 0;
481 free_menubar_widget_value_tree (first_wv);
486 t += 1000*tb.time+tb.millitm;
487 fprintf (stderr, "Menu update took %ld msec.\n", t);
492 [NSApp setMainMenu: menu];
500 /* Main emacs core entry point for menubar menus: called to indicate that the
501 frame's menus have changed, and the *step representation should be updated
504 set_frame_menubar (struct frame *f, int first_time, int deep_p)
506 ns_update_menubar (f, deep_p, nil);
510 /* ==========================================================================
512 Menu: class implementation
514 ========================================================================== */
517 /* Menu that can define itself from Emacs "widget_value"s and will lazily
518 update itself when user clicked. Based on Carbon/AppKit implementation
519 by Yamamoto Mitsuharu. */
520 @implementation EmacsMenu
522 /* override designated initializer */
523 - initWithTitle: (NSString *)title
525 if (self = [super initWithTitle: title])
526 [self setAutoenablesItems: NO];
531 /* used for top-level */
532 - initWithTitle: (NSString *)title frame: (struct frame *)f
534 [self initWithTitle: title];
537 [self setDelegate: self];
543 - (void)setFrame: (struct frame *)f
549 /* delegate method called when a submenu is being opened: run a 'deep' call
550 to set_frame_menubar */
551 - (void)menuNeedsUpdate: (NSMenu *)menu
554 if (!FRAME_LIVE_P (frame))
556 event = [[FRAME_NS_VIEW (frame) window] currentEvent];
557 /* HACK: Cocoa/Carbon will request update on every keystroke
558 via IsMenuKeyEvent -> CheckMenusForKeyEvent. These are not needed
559 since key equivalents are handled through emacs.
560 On Leopard, even keystroke events generate SystemDefined events, but
561 their subtype is 8. */
562 if ([event type] != NSSystemDefined || [event subtype] == 8
563 /* Also, don't try this if from an event picked up asynchronously,
564 as lots of lisp evaluation happens in ns_update_menubar. */
565 || handling_signal != 0)
567 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
568 ns_update_menubar (frame, 1, self);
572 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
574 if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
575 && FRAME_NS_VIEW (SELECTED_FRAME ()))
576 [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
581 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
582 into an accelerator string. We are only able to display a single character
583 for an accelerator, together with an optional modifier combination. (Under
584 Carbon more control was possible, but in Cocoa multi-char strings passed to
585 NSMenuItem get ignored. For now we try to display a super-single letter
586 combo, and return the others as strings to be appended to the item title.
587 (This is signaled by setting keyEquivModMask to 0 for now.) */
588 -(NSString *)parseKeyEquiv: (const char *)key
590 const char *tpos = key;
591 keyEquivModMask = NSCommandKeyMask;
593 if (!key || !strlen (key))
596 while (*tpos == ' ' || *tpos == '(')
598 if ((*tpos == 's') && (*(tpos+1) == '-'))
600 return [NSString stringWithFormat: @"%c", tpos[2]];
602 keyEquivModMask = 0; /* signal */
603 return [NSString stringWithUTF8String: tpos];
607 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
610 widget_value *wv = (widget_value *)wvptr;
612 if (menu_separator_name_p (wv->name))
614 item = [NSMenuItem separatorItem];
615 [self addItem: item];
619 NSString *title, *keyEq;
620 title = [NSString stringWithUTF8String: wv->name];
622 title = @"< ? >"; /* (get out in the open so we know about it) */
624 keyEq = [self parseKeyEquiv: wv->key];
626 /* OS X just ignores modifier strings longer than one character */
627 if (keyEquivModMask == 0)
628 title = [title stringByAppendingFormat: @" (%@)", keyEq];
631 item = [self addItemWithTitle: (NSString *)title
632 action: @selector (menuDown:)
633 keyEquivalent: keyEq];
634 [item setKeyEquivalentModifierMask: keyEquivModMask];
636 [item setEnabled: wv->enabled];
638 /* Draw radio buttons and tickboxes */
639 if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
640 wv->button_type == BUTTON_TYPE_RADIO))
641 [item setState: NSOnState];
643 [item setState: NSOffState];
645 [item setTag: (NSInteger)wv->call_data];
657 for (n = [self numberOfItems]-1; n >= 0; n--)
659 NSMenuItem *item = [self itemAtIndex: n];
660 NSString *title = [item title];
661 if (([title length] == 0 /* OSX 10.5 */
662 || [ns_app_name isEqualToString: title] /* from 10.6 on */
663 || [@"Apple" isEqualToString: title]) /* older */
664 && ![item isSeparatorItem])
666 [self removeItemAtIndex: n];
671 - (void)fillWithWidgetValue: (void *)wvptr
673 widget_value *wv = (widget_value *)wvptr;
675 /* clear existing contents */
676 [self setMenuChangedMessagesEnabled: NO];
679 /* add new contents */
680 for (; wv != NULL; wv = wv->next)
682 NSMenuItem *item = [self addItemWithWidgetValue: wv];
686 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
688 [self setSubmenu: submenu forItem: item];
689 [submenu fillWithWidgetValue: wv->contents];
691 [item setAction: nil];
695 [self setMenuChangedMessagesEnabled: YES];
696 #ifdef NS_IMPL_GNUSTEP
697 if ([[self window] isVisible])
700 if ([self supermenu] == nil)
706 /* adds an empty submenu and returns it */
707 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
709 NSString *titleStr = [NSString stringWithUTF8String: title];
710 NSMenuItem *item = [self addItemWithTitle: titleStr
711 action: nil /*@selector (menuDown:) */
713 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
714 [self setSubmenu: submenu forItem: item];
719 /* run a menu in popup mode */
720 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
721 keymaps: (int)keymaps
723 EmacsView *view = FRAME_NS_VIEW (f);
727 /* p = [view convertPoint:p fromView: nil]; */
728 p.y = NSHeight ([view frame]) - p.y;
729 e = [[view window] currentEvent];
730 event = [NSEvent mouseEventWithType: NSRightMouseDown
733 timestamp: [e timestamp]
734 windowNumber: [[view window] windowNumber]
736 eventNumber: 0/*[e eventNumber] */
740 context_menu_value = -1;
741 [NSMenu popUpContextMenu: self withEvent: event forView: view];
742 retVal = context_menu_value;
743 context_menu_value = 0;
745 ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
753 /* ==========================================================================
755 Context Menu: implementing functions
757 ========================================================================== */
760 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
761 Lisp_Object title, const char **error)
765 Lisp_Object window, tem, keymap;
766 int specpdl_count = SPECPDL_INDEX ();
767 widget_value *wv, *first_wv = 0;
771 /* now parse stage 2 as in ns_update_menubar */
772 wv = xmalloc_widget_value ();
773 wv->name = "contextmenu";
776 wv->button_type = BUTTON_TYPE_NONE;
781 /* FIXME: a couple of one-line differences prevent reuse */
782 wv = digest_single_submenu (0, menu_items_used, Qnil);
785 widget_value *save_wv = 0, *prev_wv = 0;
786 widget_value **submenu_stack
787 = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
788 /* Lisp_Object *subprefix_stack
789 = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
790 int submenu_depth = 0;
794 /* Loop over all panes and items, filling in the tree. */
796 while (i < menu_items_used)
798 if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
800 submenu_stack[submenu_depth++] = save_wv;
806 else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
809 save_wv = submenu_stack[--submenu_depth];
813 else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
814 && submenu_depth != 0)
815 i += MENU_ITEMS_PANE_LENGTH;
816 /* Ignore a nil in the item list.
817 It's meaningful only for dialog boxes. */
818 else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
820 else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
822 /* Create a new pane. */
823 Lisp_Object pane_name, prefix;
824 const char *pane_string;
826 pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
827 prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
829 #ifndef HAVE_MULTILINGUAL_MENU
830 if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
832 pane_name = ENCODE_MENU_STRING (pane_name);
833 ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
836 pane_string = (NILP (pane_name)
837 ? "" : (char *) SDATA (pane_name));
838 /* If there is just one top-level pane, put all its items directly
839 under the top-level menu. */
840 if (menu_items_n_panes == 1)
843 /* If the pane has a meaningful name,
844 make the pane a top-level menu item
845 with its items as a submenu beneath it. */
846 if (!keymaps && strcmp (pane_string, ""))
848 wv = xmalloc_widget_value ();
852 first_wv->contents = wv;
853 wv->name = pane_string;
854 if (keymaps && !NILP (prefix))
858 wv->button_type = BUTTON_TYPE_NONE;
869 i += MENU_ITEMS_PANE_LENGTH;
873 /* Create a new item within current pane. */
874 Lisp_Object item_name, enable, descrip, def, type, selected, help;
875 item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
876 enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
877 descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
878 def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
879 type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
880 selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
881 help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
883 #ifndef HAVE_MULTILINGUAL_MENU
884 if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
886 item_name = ENCODE_MENU_STRING (item_name);
887 ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
890 if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
892 descrip = ENCODE_MENU_STRING (descrip);
893 ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
895 #endif /* not HAVE_MULTILINGUAL_MENU */
897 wv = xmalloc_widget_value ();
901 save_wv->contents = wv;
902 wv->name = (char *) SDATA (item_name);
904 wv->key = (char *) SDATA (descrip);
906 /* If this item has a null value,
907 make the call_data null so that it won't display a box
908 when the mouse is on it. */
910 = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
911 wv->enabled = !NILP (enable);
914 wv->button_type = BUTTON_TYPE_NONE;
915 else if (EQ (type, QCtoggle))
916 wv->button_type = BUTTON_TYPE_TOGGLE;
917 else if (EQ (type, QCradio))
918 wv->button_type = BUTTON_TYPE_RADIO;
922 wv->selected = !NILP (selected);
924 if (! STRINGP (help))
931 i += MENU_ITEMS_ITEM_LENGTH;
939 widget_value *wv_title = xmalloc_widget_value ();
940 widget_value *wv_sep = xmalloc_widget_value ();
942 /* Maybe replace this separator with a bitmap or owner-draw item
943 so that it looks better. Having two separators looks odd. */
945 wv_sep->next = first_wv->contents;
948 #ifndef HAVE_MULTILINGUAL_MENU
949 if (STRING_MULTIBYTE (title))
950 title = ENCODE_MENU_STRING (title);
953 wv_title->name = (char *) SDATA (title);
954 wv_title->enabled = NO;
955 wv_title->button_type = BUTTON_TYPE_NONE;
956 wv_title->help = Qnil;
957 wv_title->next = wv_sep;
958 first_wv->contents = wv_title;
961 pmenu = [[EmacsMenu alloc] initWithTitle:
962 [NSString stringWithUTF8String: SDATA (title)]];
963 [pmenu fillWithWidgetValue: first_wv->contents];
964 free_menubar_widget_value_tree (first_wv);
965 unbind_to (specpdl_count, Qnil);
967 popup_activated_flag = 1;
968 tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
969 popup_activated_flag = 0;
970 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
976 /* ==========================================================================
978 Toolbar: externally-called functions
980 ========================================================================== */
983 free_frame_tool_bar (FRAME_PTR f)
984 /* --------------------------------------------------------------------------
985 Under NS we just hide the toolbar until it might be needed again.
986 -------------------------------------------------------------------------- */
989 [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
990 FRAME_TOOLBAR_HEIGHT (f) = 0;
995 update_frame_tool_bar (FRAME_PTR f)
996 /* --------------------------------------------------------------------------
997 Update toolbar contents
998 -------------------------------------------------------------------------- */
1001 EmacsView *view = FRAME_NS_VIEW (f);
1002 NSWindow *window = [view window];
1003 EmacsToolbar *toolbar = [view toolbar];
1006 [toolbar clearActive];
1008 /* update EmacsToolbar as in GtkUtils, build items list */
1009 for (i = 0; i < f->n_tool_bar_items; ++i)
1011 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1012 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1014 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1015 BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1020 Lisp_Object helpObj;
1021 const char *helpText;
1023 /* If image is a vector, choose the image according to the
1025 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1026 if (VECTORP (image))
1028 /* NS toolbar auto-computes disabled and selected images */
1029 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1030 xassert (ASIZE (image) >= idx);
1031 image = AREF (image, idx);
1037 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1039 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1040 helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1042 /* Ignore invalid image specifications. */
1043 if (!valid_image_p (image))
1045 /* Don't log anything, GNUS makes invalid images all the time. */
1049 img_id = lookup_image (f, image);
1050 img = IMAGE_FROM_ID (f, img_id);
1051 prepare_image_for_display (f, img);
1053 if (img->load_failed_p || img->pixmap == nil)
1055 NSLog (@"Could not prepare toolbar image for display.");
1059 [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1060 enabled: enabled_p];
1064 if (![toolbar isVisible])
1065 [toolbar setVisible: YES];
1067 if ([toolbar changed])
1069 /* inform app that toolbar has changed */
1070 NSDictionary *dict = [toolbar configurationDictionary];
1071 NSMutableDictionary *newDict = [dict mutableCopy];
1072 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1074 while ((key = [keys nextObject]) != nil)
1076 NSObject *val = [dict objectForKey: key];
1077 if ([val isKindOfClass: [NSArray class]])
1080 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1085 [toolbar setConfigurationFromDictionary: newDict];
1089 FRAME_TOOLBAR_HEIGHT (f) =
1090 NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1091 - FRAME_NS_TITLEBAR_HEIGHT (f);
1096 /* ==========================================================================
1098 Toolbar: class implementation
1100 ========================================================================== */
1102 @implementation EmacsToolbar
1104 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1106 self = [super initWithIdentifier: identifier];
1108 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1109 [self setSizeMode: NSToolbarSizeModeSmall];
1110 [self setDelegate: self];
1111 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1112 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1113 prevEnablement = enablement = 0L;
1119 [prevIdentifiers release];
1120 [activeIdentifiers release];
1121 [identifierToItem release];
1125 - (void) clearActive
1127 [prevIdentifiers release];
1128 prevIdentifiers = [activeIdentifiers copy];
1129 [activeIdentifiers removeAllObjects];
1130 prevEnablement = enablement;
1136 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1137 enablement == prevEnablement ? NO : YES;
1140 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1141 helpText: (const char *)help enabled: (BOOL)enabled
1143 /* 1) come up w/identifier */
1144 NSString *identifier
1145 = [NSString stringWithFormat: @"%u", [img hash]];
1147 /* 2) create / reuse item */
1148 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1151 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1153 [item setImage: img];
1154 [item setToolTip: [NSString stringWithUTF8String: help]];
1155 [item setTarget: emacsView];
1156 [item setAction: @selector (toolbarClicked:)];
1160 [item setEnabled: enabled];
1162 /* 3) update state */
1163 [identifierToItem setObject: item forKey: identifier];
1164 [activeIdentifiers addObject: identifier];
1165 enablement = (enablement << 1) | (enabled == YES);
1168 /* This overrides super's implementation, which automatically sets
1169 all items to enabled state (for some reason). */
1170 - (void)validateVisibleItems { }
1173 /* delegate methods */
1175 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1176 itemForItemIdentifier: (NSString *)itemIdentifier
1177 willBeInsertedIntoToolbar: (BOOL)flag
1179 /* look up NSToolbarItem by identifier and return... */
1180 return [identifierToItem objectForKey: itemIdentifier];
1183 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1185 /* return entire set.. */
1186 return activeIdentifiers;
1189 /* for configuration palette (not yet supported) */
1190 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1192 /* return entire set... */
1193 return [identifierToItem allKeys];
1196 /* optional and unneeded */
1197 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1198 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1199 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1201 @end /* EmacsToolbar */
1205 /* ==========================================================================
1207 Tooltip: class implementation
1209 ========================================================================== */
1211 /* Needed because NeXTstep does not provide enough control over tooltip
1213 @implementation EmacsTooltip
1217 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1218 blue: 0.792 alpha: 0.95];
1219 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1220 NSFont *sfont = [font screenFont];
1221 int height = [sfont ascender] - [sfont descender];
1222 /*[font boundingRectForFont].size.height; */
1223 NSRect r = NSMakeRect (0, 0, 100, height+6);
1225 textField = [[NSTextField alloc] initWithFrame: r];
1226 [textField setFont: font];
1227 [textField setBackgroundColor: col];
1229 [textField setEditable: NO];
1230 [textField setSelectable: NO];
1231 [textField setBordered: YES];
1232 [textField setBezeled: YES];
1233 [textField setDrawsBackground: YES];
1235 win = [[NSWindow alloc]
1236 initWithContentRect: [textField frame]
1238 backing: NSBackingStoreBuffered
1240 [win setReleasedWhenClosed: NO];
1241 [win setDelegate: self];
1242 [[win contentView] addSubview: textField];
1243 /* [win setBackgroundColor: col]; */
1244 [win setOpaque: NO];
1253 [textField release];
1257 - (void) setText: (char *)text
1259 NSString *str = [NSString stringWithUTF8String: text];
1260 NSRect r = [textField frame];
1261 NSSize textSize = [str sizeWithAttributes:
1262 [NSDictionary dictionaryWithObject: [[textField font] screenFont]
1263 forKey: NSFontAttributeName]];
1264 NSSize padSize = [[[textField font] screenFont]
1265 boundingRectForFont].size;
1267 r.size.width = textSize.width + padSize.width/2;
1268 r.size.height = textSize.height + padSize.height/2;
1269 [textField setFrame: r];
1270 [textField setStringValue: str];
1273 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1275 NSRect wr = [win frame];
1277 wr.origin = NSMakePoint (x, y);
1278 wr.size = [textField frame].size;
1280 [win setFrame: wr display: YES];
1281 [win orderFront: self];
1283 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1284 selector: @selector (hide)
1285 userInfo: nil repeats: NO];
1294 if ([timer isValid])
1303 return timer != nil;
1308 return [textField frame];
1311 @end /* EmacsTooltip */
1315 /* ==========================================================================
1317 Popup Dialog: implementing functions
1319 ========================================================================== */
1323 pop_down_menu (Lisp_Object arg)
1325 struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1326 if (popup_activated_flag)
1328 popup_activated_flag = 0;
1330 [NSApp endModalSession: popupSession];
1331 [((EmacsDialogPanel *) (p->pointer)) close];
1332 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1340 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1343 Lisp_Object window, tem;
1348 NSTRACE (x-popup-dialog);
1352 isQ = NILP (header);
1354 if (EQ (position, Qt)
1355 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1356 || EQ (XCAR (position), Qtool_bar))))
1358 window = selected_window;
1360 else if (CONSP (position))
1363 tem = Fcar (position);
1364 if (XTYPE (tem) == Lisp_Cons)
1365 window = Fcar (Fcdr (position));
1368 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1369 window = Fcar (tem); /* POSN_WINDOW (tem) */
1372 else if (WINDOWP (position) || FRAMEP (position))
1379 if (FRAMEP (window))
1380 f = XFRAME (window);
1381 else if (WINDOWP (window))
1383 CHECK_LIVE_WINDOW (window);
1384 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1387 CHECK_WINDOW (window);
1389 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1390 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1393 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1396 int specpdl_count = SPECPDL_INDEX ();
1397 record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1398 popup_activated_flag = 1;
1399 tem = [dialog runDialogAt: p];
1400 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
1408 /* ==========================================================================
1410 Popup Dialog: class implementation
1412 ========================================================================== */
1414 @interface FlippedView : NSView
1419 @implementation FlippedView
1426 @implementation EmacsDialogPanel
1429 #define ICONSIZE 64.0
1430 #define TEXTHEIGHT 20.0
1431 #define MINCELLWIDTH 90.0
1433 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1434 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1436 NSSize spacing = {SPACER, SPACER};
1438 char this_cmd_name[80];
1440 static NSImageView *imgView;
1441 static FlippedView *contentView;
1446 area.origin.x = 3*SPACER;
1447 area.origin.y = 2*SPACER;
1448 area.size.width = ICONSIZE;
1449 area.size.height= ICONSIZE;
1450 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1451 [img setScalesWhenResized: YES];
1452 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1453 imgView = [[NSImageView alloc] initWithFrame: area];
1454 [imgView setImage: img];
1455 [imgView setEditable: NO];
1459 aStyle = NSTitledWindowMask;
1463 [super initWithContentRect: contentRect styleMask: aStyle
1464 backing: backingType defer: flag];
1465 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1466 [self setContentView: contentView];
1468 [[self contentView] setAutoresizesSubviews: YES];
1470 [[self contentView] addSubview: imgView];
1471 [self setTitle: @""];
1473 area.origin.x += ICONSIZE+2*SPACER;
1474 /* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1475 area.size.width = 400;
1476 area.size.height= TEXTHEIGHT;
1477 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1478 [[self contentView] addSubview: command];
1479 [command setStringValue: ns_app_name];
1480 [command setDrawsBackground: NO];
1481 [command setBezeled: NO];
1482 [command setSelectable: NO];
1483 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1485 /* area.origin.x = ICONSIZE+2*SPACER;
1486 area.origin.y = TEXTHEIGHT + 2*SPACER;
1487 area.size.width = 400;
1488 area.size.height= 2;
1489 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1490 [[self contentView] addSubview: tem];
1491 [tem setTitlePosition: NSNoTitle];
1492 [tem setAutoresizingMask: NSViewWidthSizable];*/
1494 /* area.origin.x = ICONSIZE+2*SPACER; */
1495 area.origin.y += TEXTHEIGHT+SPACER;
1496 area.size.width = 400;
1497 area.size.height= TEXTHEIGHT;
1498 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1499 [[self contentView] addSubview: title];
1500 [title setDrawsBackground: NO];
1501 [title setBezeled: NO];
1502 [title setSelectable: NO];
1503 [title setFont: [NSFont systemFontOfSize: 11.0]];
1505 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1506 [cell setBordered: NO];
1507 [cell setEnabled: NO];
1508 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1509 [cell setBezelStyle: NSRoundedBezelStyle];
1511 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1512 mode: NSHighlightModeMatrix
1515 numberOfColumns: 1];
1516 [[self contentView] addSubview: matrix];
1518 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1519 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1520 [matrix setIntercellSpacing: spacing];
1522 [self setOneShot: YES];
1523 [self setReleasedWhenClosed: YES];
1524 [self setHidesOnDeactivate: YES];
1529 - (BOOL)windowShouldClose: (id)sender
1531 [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1536 void process_dialog (id window, Lisp_Object list)
1541 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1544 if (XTYPE (item) == Lisp_String)
1546 [window addString: SDATA (item) row: row++];
1548 else if (XTYPE (item) == Lisp_Cons)
1550 [window addButton: SDATA (XCAR (item))
1551 value: XCDR (item) row: row++];
1553 else if (NILP (item))
1562 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1571 cell = [matrix cellAtRow: row column: cols-1];
1572 [cell setTarget: self];
1573 [cell setAction: @selector (clicked: )];
1574 [cell setTitle: [NSString stringWithUTF8String: str]];
1575 [cell setTag: XHASH (val)]; // FIXME: BIG UGLY HACK!!
1576 [cell setBordered: YES];
1577 [cell setEnabled: YES];
1583 - addString: (char *)str row: (int)row
1592 cell = [matrix cellAtRow: row column: cols-1];
1593 [cell setTitle: [NSString stringWithUTF8String: str]];
1594 [cell setBordered: YES];
1595 [cell setEnabled: NO];
1611 NSArray *sellist = nil;
1614 sellist = [sender selectedCells];
1615 if ([sellist count]<1)
1618 seltag = [[sellist objectAtIndex: 0] tag];
1619 if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1620 [NSApp stopModalWithCode: seltag];
1625 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1630 if (XTYPE (contents) == Lisp_Cons)
1632 head = Fcar (contents);
1633 process_dialog (self, Fcdr (contents));
1638 if (XTYPE (head) == Lisp_String)
1639 [title setStringValue:
1640 [NSString stringWithUTF8String: SDATA (head)]];
1641 else if (isQ == YES)
1642 [title setStringValue: @"Question"];
1644 [title setStringValue: @"Information"];
1650 if (cols == 1 && rows > 1) /* Never told where to split */
1653 for (i = 0; i<rows/2; i++)
1655 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1656 atRow: i column: 1];
1657 [matrix removeRow: (rows+1)/2];
1663 NSSize csize = [matrix cellSize];
1664 if (csize.width < MINCELLWIDTH)
1666 csize.width = MINCELLWIDTH;
1667 [matrix setCellSize: csize];
1668 [matrix sizeToCells];
1673 [command sizeToFit];
1677 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1679 t.origin.x = r.origin.x;
1680 t.size.width = r.size.width;
1682 r = [command frame];
1683 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1685 t.origin.x = r.origin.x;
1686 t.size.width = r.size.width;
1690 s = [(NSView *)[self contentView] frame];
1691 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1692 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1693 [self setFrame: r display: NO];
1702 { [super dealloc]; return; };
1706 - (Lisp_Object)runDialogAt: (NSPoint)p
1709 extern EMACS_TIME timer_check (int do_it_now); /* TODO: add to a header */
1711 /* initiate a session that will be ended by pop_down_menu */
1712 popupSession = [NSApp beginModalSessionForWindow: self];
1713 while (popup_activated_flag
1714 && (ret = [NSApp runModalSession: popupSession])
1715 == NSRunContinuesResponse)
1717 /* Run this for timers.el, indep of atimers; might not return.
1718 TODO: use return value to avoid calling every iteration. */
1720 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1723 { /* FIXME: BIG UGLY HACK!!! */
1725 *(EMACS_INT*)(&tmp) = ret;
1733 /* ==========================================================================
1737 ========================================================================== */
1739 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1740 doc: /* Cause the NS menu to be re-calculated. */)
1743 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1748 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1749 doc: /* Pop up a dialog box and return user's selection.
1750 POSITION specifies which frame to use.
1751 This is normally a mouse button event or a window or frame.
1752 If POSITION is t, it means to use the frame the mouse is on.
1753 The dialog box appears in the middle of the specified frame.
1755 CONTENTS specifies the alternatives to display in the dialog box.
1756 It is a list of the form (DIALOG ITEM1 ITEM2...).
1757 Each ITEM is a cons cell (STRING . VALUE).
1758 The return value is VALUE from the chosen item.
1760 An ITEM may also be just a string--that makes a nonselectable item.
1761 An ITEM may also be nil--that means to put all preceding items
1762 on the left of the dialog box and all following items on the right.
1763 \(By default, approximately half appear on each side.)
1765 If HEADER is non-nil, the frame title for the box is "Information",
1766 otherwise it is "Question".
1768 If the user gets rid of the dialog box without making a valid choice,
1769 for instance using the window manager, then this produces a quit and
1770 `x-popup-dialog' does not return. */)
1771 (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1773 return ns_popup_dialog (position, contents, header);
1776 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1777 doc: /* Return t if a menu or popup dialog is active. */)
1780 return popup_activated () ? Qt : Qnil;
1783 /* ==========================================================================
1785 Lisp interface declaration
1787 ========================================================================== */
1790 syms_of_nsmenu (void)
1792 defsubr (&Sx_popup_dialog);
1793 defsubr (&Sns_reset_menu);
1794 defsubr (&Smenu_or_popup_active_p);
1796 Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1797 staticpro (&Qdebug_on_next_call);
1800 // arch-tag: 75773656-52e5-4c44-a398-47bd87b32619