1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2 Copyright (C) 2007-2012 Free Software Foundation, Inc.
4 This file is part of GNU Emacs.
6 GNU Emacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 GNU Emacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
20 By Adrian Robert, based on code from original nsmenu.m (Carl Edman,
21 Christian Limpach, Scott Bender, Christophe de Dinechin) and code in the
22 Carbon version by Yamamoto Mitsuharu. */
24 /* This should be the first include, as it may set up #defines affecting
25 interpretation of even the system includes. */
31 #include "character.h"
36 #include "blockinput.h"
38 #include "termhooks.h"
42 #define NSMENUPROFILE 0
45 #include <sys/timeb.h>
46 #include <sys/types.h>
49 #define MenuStagger 10.0
52 int menu_trace_num = 0;
53 #define NSTRACE(x) fprintf (stderr, "%s:%d: [%d] " #x "\n", \
54 __FILE__, __LINE__, ++menu_trace_num)
60 /* Include lisp -> C common menu parsing code */
61 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
62 #include "nsmenu_common.c"
65 extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
66 extern Lisp_Object QCtoggle, QCradio;
68 Lisp_Object Qdebug_on_next_call;
69 extern Lisp_Object 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 /* Nonzero means we are tracking and updating menus. */
79 static int trackingMenu;
82 /* NOTE: toolbar implementation is at end,
83 following complete menu implementation. */
86 /* ==========================================================================
88 Menu: Externally-called functions
90 ========================================================================== */
93 /* FIXME: not currently used, but should normalize with other terms. */
95 x_activate_menubar (struct frame *f)
97 fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
101 /* Supposed to discard menubar and free storage. Since we share the
102 menubar among frames and update its context for the focused window,
103 there is nothing to do here. */
105 free_frame_menubar (struct frame *f)
112 popup_activated (void)
114 return popup_activated_flag;
118 /* --------------------------------------------------------------------------
119 Update menubar. Three cases:
120 1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
121 just top-level menu strings (OS X), or goto case (2) (GNUstep).
122 2) deep_p = 1, submenu = nil: Recompute all submenus.
123 3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
124 -------------------------------------------------------------------------- */
126 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
128 NSAutoreleasePool *pool;
129 id menu = [NSApp mainMenu];
130 static EmacsMenu *last_submenu = nil;
132 const char *submenuTitle = [[submenu title] UTF8String];
133 extern int waiting_for_input;
136 widget_value *wv, *first_wv, *prev_wv = 0;
144 NSTRACE (set_frame_menubar);
146 if (f != SELECTED_FRAME ())
148 XSETFRAME (Vmenu_updating_frame, f);
149 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
152 pool = [[NSAutoreleasePool alloc] init];
154 /* Menu may have been created automatically; if so, discard it. */
155 if ([menu isKindOfClass: [EmacsMenu class]] == NO)
163 menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
167 { /* close up anything on there */
168 id attMenu = [menu attachedMenu];
175 t = -(1000*tb.time+tb.millitm);
178 #ifdef NS_IMPL_GNUSTEP
179 deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
184 /* Fully parse one or more of the submenus. */
186 int *submenu_start, *submenu_end;
187 int *submenu_top_level_items, *submenu_n_panes;
188 struct buffer *prev = current_buffer;
190 ptrdiff_t specpdl_count = SPECPDL_INDEX ();
191 int previous_menu_items_used = f->menu_bar_items_used;
192 Lisp_Object *previous_items
193 = (Lisp_Object *) alloca (previous_menu_items_used
194 * sizeof (Lisp_Object));
196 /* lisp preliminaries */
197 buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
198 specbind (Qinhibit_quit, Qt);
199 specbind (Qdebug_on_next_call, Qnil);
200 record_unwind_save_match_data ();
201 if (NILP (Voverriding_local_map_menu_flag))
203 specbind (Qoverriding_terminal_local_map, Qnil);
204 specbind (Qoverriding_local_map, Qnil);
206 set_buffer_internal_1 (XBUFFER (buffer));
208 /* TODO: for some reason this is not needed in other terms,
209 but some menu updates call Info-extract-pointer which causes
210 abort-on-error if waiting-for-input. Needs further investigation. */
211 owfi = waiting_for_input;
212 waiting_for_input = 0;
214 /* lucid hook and possible reset */
215 safe_run_hooks (Qactivate_menubar_hook);
216 if (! NILP (Vlucid_menu_bar_dirty_flag))
217 call0 (Qrecompute_lucid_menubar);
218 safe_run_hooks (Qmenu_bar_update_hook);
219 FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
221 /* Now ready to go */
222 items = FRAME_MENU_BAR_ITEMS (f);
224 /* Save the frame's previous menu bar contents data */
225 if (previous_menu_items_used)
226 memcpy (previous_items, &AREF (f->menu_bar_vector, 0),
227 previous_menu_items_used * sizeof (Lisp_Object));
229 /* parse stage 1: extract from lisp */
232 menu_items = f->menu_bar_vector;
233 menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
234 submenu_start = (int *) alloca (ASIZE (items) * sizeof (int *));
235 submenu_end = (int *) alloca (ASIZE (items) * sizeof (int *));
236 submenu_n_panes = (int *) alloca (ASIZE (items) * sizeof (int));
237 submenu_top_level_items
238 = (int *) alloca (ASIZE (items) * sizeof (int *));
240 for (i = 0; i < ASIZE (items); i += 4)
242 Lisp_Object key, string, maps;
244 key = AREF (items, i);
245 string = AREF (items, i + 1);
246 maps = AREF (items, i + 2);
250 /* FIXME: we'd like to only parse the needed submenu, but this
251 was causing crashes in the _common parsing code.. need to make
252 sure proper initialization done.. */
253 /* if (submenu && strcmp (submenuTitle, SDATA (string)))
256 submenu_start[i] = menu_items_used;
258 menu_items_n_panes = 0;
259 submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
260 submenu_n_panes[i] = menu_items_n_panes;
261 submenu_end[i] = menu_items_used;
265 finish_menu_items ();
266 waiting_for_input = owfi;
269 if (submenu && n == 0)
271 /* should have found a menu for this one but didn't */
272 fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
274 discard_menu_items ();
275 unbind_to (specpdl_count, Qnil);
281 /* parse stage 2: insert into lucid 'widget_value' structures
282 [comments in other terms say not to evaluate lisp code here] */
283 wv = xmalloc_widget_value ();
284 wv->name = "menubar";
287 wv->button_type = BUTTON_TYPE_NONE;
291 for (i = 0; i < 4*n; i += 4)
293 menu_items_n_panes = submenu_n_panes[i];
294 wv = digest_single_submenu (submenu_start[i], submenu_end[i],
295 submenu_top_level_items[i]);
299 first_wv->contents = wv;
300 /* Don't set wv->name here; GC during the loop might relocate it. */
302 wv->button_type = BUTTON_TYPE_NONE;
306 set_buffer_internal_1 (prev);
308 /* Compare the new menu items with previous, and leave off if no change */
309 /* FIXME: following other terms here, but seems like this should be
310 done before parse stage 2 above, since its results aren't used */
311 if (previous_menu_items_used
312 && (!submenu || (submenu && submenu == last_submenu))
313 && menu_items_used == previous_menu_items_used)
315 for (i = 0; i < previous_menu_items_used; i++)
316 /* FIXME: this ALWAYS fails on Buffers menu items.. something
317 about their strings causes them to change every time, so we
318 double-check failures */
319 if (!EQ (previous_items[i], AREF (menu_items, i)))
320 if (!(STRINGP (previous_items[i])
321 && STRINGP (AREF (menu_items, i))
322 && !strcmp (SDATA (previous_items[i]),
323 SDATA (AREF (menu_items, i)))))
325 if (i == previous_menu_items_used)
331 t += 1000*tb.time+tb.millitm;
332 fprintf (stderr, "NO CHANGE! CUTTING OUT after %ld msec.\n", t);
335 free_menubar_widget_value_tree (first_wv);
336 discard_menu_items ();
337 unbind_to (specpdl_count, Qnil);
343 /* The menu items are different, so store them in the frame */
344 /* FIXME: this is not correct for single-submenu case */
345 f->menu_bar_vector = menu_items;
346 f->menu_bar_items_used = menu_items_used;
348 /* Calls restore_menu_items, etc., as they were outside */
349 unbind_to (specpdl_count, Qnil);
351 /* Parse stage 2a: now GC cannot happen during the lifetime of the
352 widget_value, so it's safe to store data from a Lisp_String */
353 wv = first_wv->contents;
354 for (i = 0; i < ASIZE (items); i += 4)
357 string = AREF (items, i + 1);
360 /* if (submenu && strcmp (submenuTitle, SDATA (string)))
363 wv->name = SSDATA (string);
364 update_submenu_strings (wv->contents);
368 /* Now, update the NS menu; if we have a submenu, use that, otherwise
369 create a new menu for each sub and fill it. */
372 for (wv = first_wv->contents; wv; wv = wv->next)
374 if (!strcmp (submenuTitle, wv->name))
376 [submenu fillWithWidgetValue: wv->contents];
377 last_submenu = submenu;
384 [menu fillWithWidgetValue: first_wv->contents];
390 static int n_previous_strings = 0;
391 static char previous_strings[100][10];
392 static struct frame *last_f = NULL;
396 wv = xmalloc_widget_value ();
397 wv->name = "menubar";
400 wv->button_type = BUTTON_TYPE_NONE;
404 /* Make widget-value tree w/ just the top level menu bar strings */
405 items = FRAME_MENU_BAR_ITEMS (f);
408 free_menubar_widget_value_tree (first_wv);
415 /* check if no change.. this mechanism is a bit rough, but ready */
416 n = ASIZE (items) / 4;
417 if (f == last_f && n_previous_strings == n)
419 for (i = 0; i<n; i++)
421 string = AREF (items, 4*i+1);
423 if (EQ (string, make_number (0))) // FIXME: Why??? --Stef
426 if (previous_strings[i][0])
430 if (strncmp (previous_strings[i], SDATA (string), 10))
436 free_menubar_widget_value_tree (first_wv);
444 for (i = 0; i < ASIZE (items); i += 4)
446 string = AREF (items, i + 1);
451 strncpy (previous_strings[i/4], SDATA (string), 10);
453 wv = xmalloc_widget_value ();
454 wv->name = SSDATA (string);
457 wv->button_type = BUTTON_TYPE_NONE;
459 wv->call_data = (void *) (intptr_t) (-1);
462 /* we'll update the real copy under app menu when time comes */
463 if (!strcmp ("Services", wv->name))
465 /* but we need to make sure it will update on demand */
466 [svcsMenu setFrame: f];
470 [menu addSubmenuWithTitle: wv->name forFrame: f];
475 first_wv->contents = wv;
481 n_previous_strings = n;
483 n_previous_strings = 0;
486 free_menubar_widget_value_tree (first_wv);
491 t += 1000*tb.time+tb.millitm;
492 fprintf (stderr, "Menu update took %ld msec.\n", t);
497 [NSApp setMainMenu: menu];
505 /* Main emacs core entry point for menubar menus: called to indicate that the
506 frame's menus have changed, and the *step representation should be updated
509 set_frame_menubar (struct frame *f, int first_time, int deep_p)
511 ns_update_menubar (f, deep_p, nil);
515 /* ==========================================================================
517 Menu: class implementation
519 ========================================================================== */
522 /* Menu that can define itself from Emacs "widget_value"s and will lazily
523 update itself when user clicked. Based on Carbon/AppKit implementation
524 by Yamamoto Mitsuharu. */
525 @implementation EmacsMenu
527 /* override designated initializer */
528 - initWithTitle: (NSString *)title
530 if (self = [super initWithTitle: title])
531 [self setAutoenablesItems: NO];
536 /* used for top-level */
537 - initWithTitle: (NSString *)title frame: (struct frame *)f
539 [self initWithTitle: title];
542 [self setDelegate: self];
548 - (void)setFrame: (struct frame *)f
554 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
555 extern NSString *NSMenuDidBeginTrackingNotification;
560 -(void)trackingNotification:(NSNotification *)notification
562 /* Update menu in menuNeedsUpdate only while tracking menus. */
563 trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
568 /* delegate method called when a submenu is being opened: run a 'deep' call
569 to set_frame_menubar */
570 - (void)menuNeedsUpdate: (NSMenu *)menu
572 if (!FRAME_LIVE_P (frame))
575 /* Cocoa/Carbon will request update on every keystroke
576 via IsMenuKeyEvent -> CheckMenusForKeyEvent. These are not needed
577 since key equivalents are handled through emacs.
578 On Leopard, even keystroke events generate SystemDefined event.
579 Third-party applications that enhance mouse / trackpad
580 interaction, or also VNC/Remote Desktop will send events
581 of type AppDefined rather than SysDefined.
582 Menus will fail to show up if they haven't been initialized.
583 AppDefined events may lack timing data.
585 Thus, we rely on the didBeginTrackingNotification notification
586 as above to indicate the need for updates.
587 From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
588 key press case, NSMenuPropertyItemImage (e.g.) won't be set.
590 if (trackingMenu == 0
591 /* Also, don't try this if from an event picked up asynchronously,
592 as lots of lisp evaluation happens in ns_update_menubar. */
593 || handling_signal != 0)
595 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
596 ns_update_menubar (frame, 1, self);
600 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
602 if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
603 && FRAME_NS_VIEW (SELECTED_FRAME ()))
604 [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
609 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
610 into an accelerator string. We are only able to display a single character
611 for an accelerator, together with an optional modifier combination. (Under
612 Carbon more control was possible, but in Cocoa multi-char strings passed to
613 NSMenuItem get ignored. For now we try to display a super-single letter
614 combo, and return the others as strings to be appended to the item title.
615 (This is signaled by setting keyEquivModMask to 0 for now.) */
616 -(NSString *)parseKeyEquiv: (const char *)key
618 const char *tpos = key;
619 keyEquivModMask = NSCommandKeyMask;
621 if (!key || !strlen (key))
624 while (*tpos == ' ' || *tpos == '(')
626 if ((*tpos == 's') && (*(tpos+1) == '-'))
628 return [NSString stringWithFormat: @"%c", tpos[2]];
630 keyEquivModMask = 0; /* signal */
631 return [NSString stringWithUTF8String: tpos];
635 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
638 widget_value *wv = (widget_value *)wvptr;
640 if (menu_separator_name_p (wv->name))
642 item = [NSMenuItem separatorItem];
643 [self addItem: item];
647 NSString *title, *keyEq;
648 title = [NSString stringWithUTF8String: wv->name];
650 title = @"< ? >"; /* (get out in the open so we know about it) */
652 keyEq = [self parseKeyEquiv: wv->key];
654 /* OS X just ignores modifier strings longer than one character */
655 if (keyEquivModMask == 0)
656 title = [title stringByAppendingFormat: @" (%@)", keyEq];
659 item = [self addItemWithTitle: (NSString *)title
660 action: @selector (menuDown:)
661 keyEquivalent: keyEq];
662 [item setKeyEquivalentModifierMask: keyEquivModMask];
664 [item setEnabled: wv->enabled];
666 /* Draw radio buttons and tickboxes */
667 if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
668 wv->button_type == BUTTON_TYPE_RADIO))
669 [item setState: NSOnState];
671 [item setState: NSOffState];
673 [item setTag: (NSInteger)wv->call_data];
685 for (n = [self numberOfItems]-1; n >= 0; n--)
687 NSMenuItem *item = [self itemAtIndex: n];
688 NSString *title = [item title];
689 if (([title length] == 0 /* OSX 10.5 */
690 || [ns_app_name isEqualToString: title] /* from 10.6 on */
691 || [@"Apple" isEqualToString: title]) /* older */
692 && ![item isSeparatorItem])
694 [self removeItemAtIndex: n];
699 - (void)fillWithWidgetValue: (void *)wvptr
701 widget_value *wv = (widget_value *)wvptr;
703 /* clear existing contents */
704 [self setMenuChangedMessagesEnabled: NO];
707 /* add new contents */
708 for (; wv != NULL; wv = wv->next)
710 NSMenuItem *item = [self addItemWithWidgetValue: wv];
714 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
716 [self setSubmenu: submenu forItem: item];
717 [submenu fillWithWidgetValue: wv->contents];
719 [item setAction: nil];
723 [self setMenuChangedMessagesEnabled: YES];
724 #ifdef NS_IMPL_GNUSTEP
725 if ([[self window] isVisible])
728 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_2
729 if ([self supermenu] == nil)
736 /* adds an empty submenu and returns it */
737 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
739 NSString *titleStr = [NSString stringWithUTF8String: title];
740 NSMenuItem *item = [self addItemWithTitle: titleStr
741 action: nil /*@selector (menuDown:) */
743 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
744 [self setSubmenu: submenu forItem: item];
749 /* run a menu in popup mode */
750 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
751 keymaps: (int)keymaps
753 EmacsView *view = FRAME_NS_VIEW (f);
757 /* p = [view convertPoint:p fromView: nil]; */
758 p.y = NSHeight ([view frame]) - p.y;
759 e = [[view window] currentEvent];
760 event = [NSEvent mouseEventWithType: NSRightMouseDown
763 timestamp: [e timestamp]
764 windowNumber: [[view window] windowNumber]
766 eventNumber: 0/*[e eventNumber] */
770 context_menu_value = -1;
771 [NSMenu popUpContextMenu: self withEvent: event forView: view];
772 retVal = context_menu_value;
773 context_menu_value = 0;
775 ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
783 /* ==========================================================================
785 Context Menu: implementing functions
787 ========================================================================== */
790 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
791 Lisp_Object title, const char **error)
795 Lisp_Object window, tem, keymap;
796 ptrdiff_t specpdl_count = SPECPDL_INDEX ();
797 widget_value *wv, *first_wv = 0;
801 /* now parse stage 2 as in ns_update_menubar */
802 wv = xmalloc_widget_value ();
803 wv->name = "contextmenu";
806 wv->button_type = BUTTON_TYPE_NONE;
811 /* FIXME: a couple of one-line differences prevent reuse */
812 wv = digest_single_submenu (0, menu_items_used, Qnil);
815 widget_value *save_wv = 0, *prev_wv = 0;
816 widget_value **submenu_stack
817 = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
818 /* Lisp_Object *subprefix_stack
819 = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
820 int submenu_depth = 0;
824 /* Loop over all panes and items, filling in the tree. */
826 while (i < menu_items_used)
828 if (EQ (AREF (menu_items, i), Qnil))
830 submenu_stack[submenu_depth++] = save_wv;
836 else if (EQ (AREF (menu_items, i), Qlambda))
839 save_wv = submenu_stack[--submenu_depth];
843 else if (EQ (AREF (menu_items, i), Qt)
844 && submenu_depth != 0)
845 i += MENU_ITEMS_PANE_LENGTH;
846 /* Ignore a nil in the item list.
847 It's meaningful only for dialog boxes. */
848 else if (EQ (AREF (menu_items, i), Qquote))
850 else if (EQ (AREF (menu_items, i), Qt))
852 /* Create a new pane. */
853 Lisp_Object pane_name, prefix;
854 const char *pane_string;
856 pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
857 prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
859 #ifndef HAVE_MULTILINGUAL_MENU
860 if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
862 pane_name = ENCODE_MENU_STRING (pane_name);
863 ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
866 pane_string = (NILP (pane_name)
867 ? "" : SSDATA (pane_name));
868 /* If there is just one top-level pane, put all its items directly
869 under the top-level menu. */
870 if (menu_items_n_panes == 1)
873 /* If the pane has a meaningful name,
874 make the pane a top-level menu item
875 with its items as a submenu beneath it. */
876 if (!keymaps && strcmp (pane_string, ""))
878 wv = xmalloc_widget_value ();
882 first_wv->contents = wv;
883 wv->name = pane_string;
884 if (keymaps && !NILP (prefix))
888 wv->button_type = BUTTON_TYPE_NONE;
899 i += MENU_ITEMS_PANE_LENGTH;
903 /* Create a new item within current pane. */
904 Lisp_Object item_name, enable, descrip, def, type, selected, help;
905 item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
906 enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
907 descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
908 def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
909 type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
910 selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
911 help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
913 #ifndef HAVE_MULTILINGUAL_MENU
914 if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
916 item_name = ENCODE_MENU_STRING (item_name);
917 ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
920 if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
922 descrip = ENCODE_MENU_STRING (descrip);
923 ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
925 #endif /* not HAVE_MULTILINGUAL_MENU */
927 wv = xmalloc_widget_value ();
931 save_wv->contents = wv;
932 wv->name = SSDATA (item_name);
934 wv->key = SSDATA (descrip);
936 /* If this item has a null value,
937 make the call_data null so that it won't display a box
938 when the mouse is on it. */
940 = !NILP (def) ? (void *) &AREF (menu_items, i) : 0;
941 wv->enabled = !NILP (enable);
944 wv->button_type = BUTTON_TYPE_NONE;
945 else if (EQ (type, QCtoggle))
946 wv->button_type = BUTTON_TYPE_TOGGLE;
947 else if (EQ (type, QCradio))
948 wv->button_type = BUTTON_TYPE_RADIO;
952 wv->selected = !NILP (selected);
954 if (! STRINGP (help))
961 i += MENU_ITEMS_ITEM_LENGTH;
969 widget_value *wv_title = xmalloc_widget_value ();
970 widget_value *wv_sep = xmalloc_widget_value ();
972 /* Maybe replace this separator with a bitmap or owner-draw item
973 so that it looks better. Having two separators looks odd. */
975 wv_sep->next = first_wv->contents;
978 #ifndef HAVE_MULTILINGUAL_MENU
979 if (STRING_MULTIBYTE (title))
980 title = ENCODE_MENU_STRING (title);
983 wv_title->name = SSDATA (title);
984 wv_title->enabled = NO;
985 wv_title->button_type = BUTTON_TYPE_NONE;
986 wv_title->help = Qnil;
987 wv_title->next = wv_sep;
988 first_wv->contents = wv_title;
991 pmenu = [[EmacsMenu alloc] initWithTitle:
992 [NSString stringWithUTF8String: SDATA (title)]];
993 [pmenu fillWithWidgetValue: first_wv->contents];
994 free_menubar_widget_value_tree (first_wv);
995 unbind_to (specpdl_count, Qnil);
997 popup_activated_flag = 1;
998 tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
999 popup_activated_flag = 0;
1000 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1006 /* ==========================================================================
1008 Toolbar: externally-called functions
1010 ========================================================================== */
1013 free_frame_tool_bar (FRAME_PTR f)
1014 /* --------------------------------------------------------------------------
1015 Under NS we just hide the toolbar until it might be needed again.
1016 -------------------------------------------------------------------------- */
1019 [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1020 FRAME_TOOLBAR_HEIGHT (f) = 0;
1025 update_frame_tool_bar (FRAME_PTR f)
1026 /* --------------------------------------------------------------------------
1027 Update toolbar contents
1028 -------------------------------------------------------------------------- */
1031 EmacsView *view = FRAME_NS_VIEW (f);
1032 NSWindow *window = [view window];
1033 EmacsToolbar *toolbar = [view toolbar];
1036 [toolbar clearActive];
1038 /* update EmacsToolbar as in GtkUtils, build items list */
1039 for (i = 0; i < f->n_tool_bar_items; ++i)
1041 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1042 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1044 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1045 BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1050 Lisp_Object helpObj;
1051 const char *helpText;
1053 /* If image is a vector, choose the image according to the
1055 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1056 if (VECTORP (image))
1058 /* NS toolbar auto-computes disabled and selected images */
1059 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1060 xassert (ASIZE (image) >= idx);
1061 image = AREF (image, idx);
1067 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1069 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1070 helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1072 /* Ignore invalid image specifications. */
1073 if (!valid_image_p (image))
1075 /* Don't log anything, GNUS makes invalid images all the time. */
1079 img_id = lookup_image (f, image);
1080 img = IMAGE_FROM_ID (f, img_id);
1081 prepare_image_for_display (f, img);
1083 if (img->load_failed_p || img->pixmap == nil)
1085 NSLog (@"Could not prepare toolbar image for display.");
1089 [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1090 enabled: enabled_p];
1094 if (![toolbar isVisible])
1095 [toolbar setVisible: YES];
1097 if ([toolbar changed])
1099 /* inform app that toolbar has changed */
1100 NSDictionary *dict = [toolbar configurationDictionary];
1101 NSMutableDictionary *newDict = [dict mutableCopy];
1102 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1104 while ((key = [keys nextObject]) != nil)
1106 NSObject *val = [dict objectForKey: key];
1107 if ([val isKindOfClass: [NSArray class]])
1110 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1115 [toolbar setConfigurationFromDictionary: newDict];
1119 FRAME_TOOLBAR_HEIGHT (f) =
1120 NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1121 - FRAME_NS_TITLEBAR_HEIGHT (f);
1126 /* ==========================================================================
1128 Toolbar: class implementation
1130 ========================================================================== */
1132 @implementation EmacsToolbar
1134 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1136 self = [super initWithIdentifier: identifier];
1138 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1139 [self setSizeMode: NSToolbarSizeModeSmall];
1140 [self setDelegate: self];
1141 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1142 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1143 prevEnablement = enablement = 0L;
1149 [prevIdentifiers release];
1150 [activeIdentifiers release];
1151 [identifierToItem release];
1155 - (void) clearActive
1157 [prevIdentifiers release];
1158 prevIdentifiers = [activeIdentifiers copy];
1159 [activeIdentifiers removeAllObjects];
1160 prevEnablement = enablement;
1166 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1167 enablement == prevEnablement ? NO : YES;
1170 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1171 helpText: (const char *)help enabled: (BOOL)enabled
1173 /* 1) come up w/identifier */
1174 NSString *identifier
1175 = [NSString stringWithFormat: @"%u", [img hash]];
1177 /* 2) create / reuse item */
1178 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1181 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1183 [item setImage: img];
1184 [item setToolTip: [NSString stringWithUTF8String: help]];
1185 [item setTarget: emacsView];
1186 [item setAction: @selector (toolbarClicked:)];
1190 [item setEnabled: enabled];
1192 /* 3) update state */
1193 [identifierToItem setObject: item forKey: identifier];
1194 [activeIdentifiers addObject: identifier];
1195 enablement = (enablement << 1) | (enabled == YES);
1198 /* This overrides super's implementation, which automatically sets
1199 all items to enabled state (for some reason). */
1200 - (void)validateVisibleItems { }
1203 /* delegate methods */
1205 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1206 itemForItemIdentifier: (NSString *)itemIdentifier
1207 willBeInsertedIntoToolbar: (BOOL)flag
1209 /* look up NSToolbarItem by identifier and return... */
1210 return [identifierToItem objectForKey: itemIdentifier];
1213 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1215 /* return entire set.. */
1216 return activeIdentifiers;
1219 /* for configuration palette (not yet supported) */
1220 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1222 /* return entire set... */
1223 return [identifierToItem allKeys];
1226 /* optional and unneeded */
1227 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1228 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1229 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1231 @end /* EmacsToolbar */
1235 /* ==========================================================================
1237 Tooltip: class implementation
1239 ========================================================================== */
1241 /* Needed because NeXTstep does not provide enough control over tooltip
1243 @implementation EmacsTooltip
1247 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1248 blue: 0.792 alpha: 0.95];
1249 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1250 NSFont *sfont = [font screenFont];
1251 int height = [sfont ascender] - [sfont descender];
1252 /*[font boundingRectForFont].size.height; */
1253 NSRect r = NSMakeRect (0, 0, 100, height+6);
1255 textField = [[NSTextField alloc] initWithFrame: r];
1256 [textField setFont: font];
1257 [textField setBackgroundColor: col];
1259 [textField setEditable: NO];
1260 [textField setSelectable: NO];
1261 [textField setBordered: NO];
1262 [textField setBezeled: NO];
1263 [textField setDrawsBackground: YES];
1265 win = [[NSWindow alloc]
1266 initWithContentRect: [textField frame]
1268 backing: NSBackingStoreBuffered
1270 [win setHasShadow: YES];
1271 [win setReleasedWhenClosed: NO];
1272 [win setDelegate: self];
1273 [[win contentView] addSubview: textField];
1274 /* [win setBackgroundColor: col]; */
1275 [win setOpaque: NO];
1284 [textField release];
1288 - (void) setText: (char *)text
1290 NSString *str = [NSString stringWithUTF8String: text];
1291 NSRect r = [textField frame];
1294 [textField setStringValue: str];
1295 tooltipDims = [[textField cell] cellSize];
1297 r.size.width = tooltipDims.width;
1298 r.size.height = tooltipDims.height;
1299 [textField setFrame: r];
1302 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1304 NSRect wr = [win frame];
1306 wr.origin = NSMakePoint (x, y);
1307 wr.size = [textField frame].size;
1309 [win setFrame: wr display: YES];
1310 [win orderFront: self];
1312 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1313 selector: @selector (hide)
1314 userInfo: nil repeats: NO];
1323 if ([timer isValid])
1332 return timer != nil;
1337 return [textField frame];
1340 @end /* EmacsTooltip */
1344 /* ==========================================================================
1346 Popup Dialog: implementing functions
1348 ========================================================================== */
1352 pop_down_menu (Lisp_Object arg)
1354 struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1355 if (popup_activated_flag)
1357 popup_activated_flag = 0;
1359 [NSApp endModalSession: popupSession];
1360 [((EmacsDialogPanel *) (p->pointer)) close];
1361 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1369 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1372 Lisp_Object window, tem, title;
1377 NSTRACE (x-popup-dialog);
1381 isQ = NILP (header);
1383 if (EQ (position, Qt)
1384 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1385 || EQ (XCAR (position), Qtool_bar))))
1387 window = selected_window;
1389 else if (CONSP (position))
1392 tem = Fcar (position);
1393 if (XTYPE (tem) == Lisp_Cons)
1394 window = Fcar (Fcdr (position));
1397 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1398 window = Fcar (tem); /* POSN_WINDOW (tem) */
1401 else if (WINDOWP (position) || FRAMEP (position))
1408 if (FRAMEP (window))
1409 f = XFRAME (window);
1410 else if (WINDOWP (window))
1412 CHECK_LIVE_WINDOW (window);
1413 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1416 CHECK_WINDOW (window);
1418 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1419 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1421 title = Fcar (contents);
1422 CHECK_STRING (title);
1424 if (NILP (Fcar (Fcdr (contents))))
1425 /* No buttons specified, add an "Ok" button so users can pop down
1427 contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1430 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1433 ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1434 record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1435 popup_activated_flag = 1;
1436 tem = [dialog runDialogAt: p];
1437 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
1445 /* ==========================================================================
1447 Popup Dialog: class implementation
1449 ========================================================================== */
1451 @interface FlippedView : NSView
1456 @implementation FlippedView
1463 @implementation EmacsDialogPanel
1466 #define ICONSIZE 64.0
1467 #define TEXTHEIGHT 20.0
1468 #define MINCELLWIDTH 90.0
1470 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1471 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1473 NSSize spacing = {SPACER, SPACER};
1475 char this_cmd_name[80];
1477 static NSImageView *imgView;
1478 static FlippedView *contentView;
1483 area.origin.x = 3*SPACER;
1484 area.origin.y = 2*SPACER;
1485 area.size.width = ICONSIZE;
1486 area.size.height= ICONSIZE;
1487 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1488 [img setScalesWhenResized: YES];
1489 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1490 imgView = [[NSImageView alloc] initWithFrame: area];
1491 [imgView setImage: img];
1492 [imgView setEditable: NO];
1496 aStyle = NSTitledWindowMask;
1500 [super initWithContentRect: contentRect styleMask: aStyle
1501 backing: backingType defer: flag];
1502 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1503 [self setContentView: contentView];
1505 [[self contentView] setAutoresizesSubviews: YES];
1507 [[self contentView] addSubview: imgView];
1508 [self setTitle: @""];
1510 area.origin.x += ICONSIZE+2*SPACER;
1511 /* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1512 area.size.width = 400;
1513 area.size.height= TEXTHEIGHT;
1514 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1515 [[self contentView] addSubview: command];
1516 [command setStringValue: ns_app_name];
1517 [command setDrawsBackground: NO];
1518 [command setBezeled: NO];
1519 [command setSelectable: NO];
1520 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1522 /* area.origin.x = ICONSIZE+2*SPACER;
1523 area.origin.y = TEXTHEIGHT + 2*SPACER;
1524 area.size.width = 400;
1525 area.size.height= 2;
1526 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1527 [[self contentView] addSubview: tem];
1528 [tem setTitlePosition: NSNoTitle];
1529 [tem setAutoresizingMask: NSViewWidthSizable];*/
1531 /* area.origin.x = ICONSIZE+2*SPACER; */
1532 area.origin.y += TEXTHEIGHT+SPACER;
1533 area.size.width = 400;
1534 area.size.height= TEXTHEIGHT;
1535 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1536 [[self contentView] addSubview: title];
1537 [title setDrawsBackground: NO];
1538 [title setBezeled: NO];
1539 [title setSelectable: NO];
1540 [title setFont: [NSFont systemFontOfSize: 11.0]];
1542 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1543 [cell setBordered: NO];
1544 [cell setEnabled: NO];
1545 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1546 [cell setBezelStyle: NSRoundedBezelStyle];
1548 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1549 mode: NSHighlightModeMatrix
1552 numberOfColumns: 1];
1553 [[self contentView] addSubview: matrix];
1555 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1556 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1557 [matrix setIntercellSpacing: spacing];
1559 [self setOneShot: YES];
1560 [self setReleasedWhenClosed: YES];
1561 [self setHidesOnDeactivate: YES];
1566 - (BOOL)windowShouldClose: (id)sender
1568 [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1573 void process_dialog (id window, Lisp_Object list)
1578 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1581 if (XTYPE (item) == Lisp_String)
1583 [window addString: SDATA (item) row: row++];
1585 else if (XTYPE (item) == Lisp_Cons)
1587 [window addButton: SDATA (XCAR (item))
1588 value: XCDR (item) row: row++];
1590 else if (NILP (item))
1599 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1608 cell = [matrix cellAtRow: row column: cols-1];
1609 [cell setTarget: self];
1610 [cell setAction: @selector (clicked: )];
1611 [cell setTitle: [NSString stringWithUTF8String: str]];
1612 [cell setTag: XHASH (val)]; // FIXME: BIG UGLY HACK!!
1613 [cell setBordered: YES];
1614 [cell setEnabled: YES];
1620 - addString: (char *)str row: (int)row
1629 cell = [matrix cellAtRow: row column: cols-1];
1630 [cell setTitle: [NSString stringWithUTF8String: str]];
1631 [cell setBordered: YES];
1632 [cell setEnabled: NO];
1648 NSArray *sellist = nil;
1651 sellist = [sender selectedCells];
1652 if ([sellist count]<1)
1655 seltag = [[sellist objectAtIndex: 0] tag];
1656 if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1657 [NSApp stopModalWithCode: seltag];
1662 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1667 if (XTYPE (contents) == Lisp_Cons)
1669 head = Fcar (contents);
1670 process_dialog (self, Fcdr (contents));
1675 if (XTYPE (head) == Lisp_String)
1676 [title setStringValue:
1677 [NSString stringWithUTF8String: SDATA (head)]];
1678 else if (isQ == YES)
1679 [title setStringValue: @"Question"];
1681 [title setStringValue: @"Information"];
1687 if (cols == 1 && rows > 1) /* Never told where to split */
1690 for (i = 0; i<rows/2; i++)
1692 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1693 atRow: i column: 1];
1694 [matrix removeRow: (rows+1)/2];
1700 NSSize csize = [matrix cellSize];
1701 if (csize.width < MINCELLWIDTH)
1703 csize.width = MINCELLWIDTH;
1704 [matrix setCellSize: csize];
1705 [matrix sizeToCells];
1710 [command sizeToFit];
1714 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1716 t.origin.x = r.origin.x;
1717 t.size.width = r.size.width;
1719 r = [command frame];
1720 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1722 t.origin.x = r.origin.x;
1723 t.size.width = r.size.width;
1727 s = [(NSView *)[self contentView] frame];
1728 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1729 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1730 [self setFrame: r display: NO];
1739 { [super dealloc]; return; };
1743 - (Lisp_Object)runDialogAt: (NSPoint)p
1747 /* initiate a session that will be ended by pop_down_menu */
1748 popupSession = [NSApp beginModalSessionForWindow: self];
1749 while (popup_activated_flag
1750 && (ret = [NSApp runModalSession: popupSession])
1751 == NSRunContinuesResponse)
1753 /* Run this for timers.el, indep of atimers; might not return.
1754 TODO: use return value to avoid calling every iteration. */
1756 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1759 { /* FIXME: BIG UGLY HACK!!! */
1761 *(EMACS_INT*)(&tmp) = ret;
1769 /* ==========================================================================
1773 ========================================================================== */
1775 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1776 doc: /* Cause the NS menu to be re-calculated. */)
1779 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1784 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1785 doc: /* Pop up a dialog box and return user's selection.
1786 POSITION specifies which frame to use.
1787 This is normally a mouse button event or a window or frame.
1788 If POSITION is t, it means to use the frame the mouse is on.
1789 The dialog box appears in the middle of the specified frame.
1791 CONTENTS specifies the alternatives to display in the dialog box.
1792 It is a list of the form (DIALOG ITEM1 ITEM2...).
1793 Each ITEM is a cons cell (STRING . VALUE).
1794 The return value is VALUE from the chosen item.
1796 An ITEM may also be just a string--that makes a nonselectable item.
1797 An ITEM may also be nil--that means to put all preceding items
1798 on the left of the dialog box and all following items on the right.
1799 \(By default, approximately half appear on each side.)
1801 If HEADER is non-nil, the frame title for the box is "Information",
1802 otherwise it is "Question".
1804 If the user gets rid of the dialog box without making a valid choice,
1805 for instance using the window manager, then this produces a quit and
1806 `x-popup-dialog' does not return. */)
1807 (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1809 return ns_popup_dialog (position, contents, header);
1812 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1813 doc: /* Return t if a menu or popup dialog is active. */)
1816 return popup_activated () ? Qt : Qnil;
1819 /* ==========================================================================
1821 Lisp interface declaration
1823 ========================================================================== */
1826 syms_of_nsmenu (void)
1828 #ifndef NS_IMPL_COCOA
1829 /* Don't know how to keep track of this in Next/Open/Gnustep. Always
1830 update menus there. */
1833 defsubr (&Sx_popup_dialog);
1834 defsubr (&Sns_reset_menu);
1835 defsubr (&Smenu_or_popup_active_p);
1837 Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1838 staticpro (&Qdebug_on_next_call);