1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2 Copyright (C) 2007-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 Qoverriding_local_map, Qoverriding_terminal_local_map;
70 extern long context_menu_value;
71 EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
73 /* Nonzero means a menu is currently active. */
74 static int popup_activated_flag;
75 static NSModalSession popupSession;
77 /* Nonzero means we are tracking and updating menus. */
78 static int trackingMenu;
81 /* NOTE: toolbar implementation is at end,
82 following complete menu implementation. */
85 /* ==========================================================================
87 Menu: Externally-called functions
89 ========================================================================== */
92 /* FIXME: not currently used, but should normalize with other terms. */
94 x_activate_menubar (struct frame *f)
96 fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
100 /* Supposed to discard menubar and free storage. Since we share the
101 menubar among frames and update its context for the focused window,
102 there is nothing to do here. */
104 free_frame_menubar (struct frame *f)
111 popup_activated (void)
113 return popup_activated_flag;
117 /* --------------------------------------------------------------------------
118 Update menubar. Three cases:
119 1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
120 just top-level menu strings (OS X), or goto case (2) (GNUstep).
121 2) deep_p = 1, submenu = nil: Recompute all submenus.
122 3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
123 -------------------------------------------------------------------------- */
125 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
127 NSAutoreleasePool *pool;
128 id menu = [NSApp mainMenu];
129 static EmacsMenu *last_submenu = nil;
131 const char *submenuTitle = [[submenu title] UTF8String];
132 extern int waiting_for_input;
135 widget_value *wv, *first_wv, *prev_wv = 0;
143 NSTRACE (set_frame_menubar);
145 if (f != SELECTED_FRAME ())
147 XSETFRAME (Vmenu_updating_frame, f);
148 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
151 pool = [[NSAutoreleasePool alloc] init];
153 /* Menu may have been created automatically; if so, discard it. */
154 if ([menu isKindOfClass: [EmacsMenu class]] == NO)
162 menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
166 { /* close up anything on there */
167 id attMenu = [menu attachedMenu];
174 t = -(1000*tb.time+tb.millitm);
177 #ifdef NS_IMPL_GNUSTEP
178 deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
183 /* Fully parse one or more of the submenus. */
185 int *submenu_start, *submenu_end;
186 int *submenu_top_level_items, *submenu_n_panes;
187 struct buffer *prev = current_buffer;
189 int specpdl_count = SPECPDL_INDEX ();
190 int previous_menu_items_used = f->menu_bar_items_used;
191 Lisp_Object *previous_items
192 = (Lisp_Object *) alloca (previous_menu_items_used
193 * sizeof (Lisp_Object));
195 /* lisp preliminaries */
196 buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
197 specbind (Qinhibit_quit, Qt);
198 specbind (Qdebug_on_next_call, Qnil);
199 record_unwind_save_match_data ();
200 if (NILP (Voverriding_local_map_menu_flag))
202 specbind (Qoverriding_terminal_local_map, Qnil);
203 specbind (Qoverriding_local_map, Qnil);
205 set_buffer_internal_1 (XBUFFER (buffer));
207 /* TODO: for some reason this is not needed in other terms,
208 but some menu updates call Info-extract-pointer which causes
209 abort-on-error if waiting-for-input. Needs further investigation. */
210 owfi = waiting_for_input;
211 waiting_for_input = 0;
213 /* lucid hook and possible reset */
214 safe_run_hooks (Qactivate_menubar_hook);
215 if (! NILP (Vlucid_menu_bar_dirty_flag))
216 call0 (Qrecompute_lucid_menubar);
217 safe_run_hooks (Qmenu_bar_update_hook);
218 FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
220 /* Now ready to go */
221 items = FRAME_MENU_BAR_ITEMS (f);
223 /* Save the frame's previous menu bar contents data */
224 if (previous_menu_items_used)
225 memcpy (previous_items, &AREF (f->menu_bar_vector, 0),
226 previous_menu_items_used * sizeof (Lisp_Object));
228 /* parse stage 1: extract from lisp */
231 menu_items = f->menu_bar_vector;
232 menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
233 submenu_start = (int *) alloca (ASIZE (items) * sizeof (int *));
234 submenu_end = (int *) alloca (ASIZE (items) * sizeof (int *));
235 submenu_n_panes = (int *) alloca (ASIZE (items) * sizeof (int));
236 submenu_top_level_items
237 = (int *) alloca (ASIZE (items) * sizeof (int *));
239 for (i = 0; i < ASIZE (items); i += 4)
241 Lisp_Object key, string, maps;
243 key = AREF (items, i);
244 string = AREF (items, i + 1);
245 maps = AREF (items, i + 2);
249 /* FIXME: we'd like to only parse the needed submenu, but this
250 was causing crashes in the _common parsing code.. need to make
251 sure proper initialization done.. */
252 /* if (submenu && strcmp (submenuTitle, SDATA (string)))
255 submenu_start[i] = menu_items_used;
257 menu_items_n_panes = 0;
258 submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
259 submenu_n_panes[i] = menu_items_n_panes;
260 submenu_end[i] = menu_items_used;
264 finish_menu_items ();
265 waiting_for_input = owfi;
268 if (submenu && n == 0)
270 /* should have found a menu for this one but didn't */
271 fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
273 discard_menu_items ();
274 unbind_to (specpdl_count, Qnil);
280 /* parse stage 2: insert into lucid 'widget_value' structures
281 [comments in other terms say not to evaluate lisp code here] */
282 wv = xmalloc_widget_value ();
283 wv->name = "menubar";
286 wv->button_type = BUTTON_TYPE_NONE;
290 for (i = 0; i < 4*n; i += 4)
292 menu_items_n_panes = submenu_n_panes[i];
293 wv = digest_single_submenu (submenu_start[i], submenu_end[i],
294 submenu_top_level_items[i]);
298 first_wv->contents = wv;
299 /* Don't set wv->name here; GC during the loop might relocate it. */
301 wv->button_type = BUTTON_TYPE_NONE;
305 set_buffer_internal_1 (prev);
307 /* Compare the new menu items with previous, and leave off if no change */
308 /* FIXME: following other terms here, but seems like this should be
309 done before parse stage 2 above, since its results aren't used */
310 if (previous_menu_items_used
311 && (!submenu || (submenu && submenu == last_submenu))
312 && menu_items_used == previous_menu_items_used)
314 for (i = 0; i < previous_menu_items_used; i++)
315 /* FIXME: this ALWAYS fails on Buffers menu items.. something
316 about their strings causes them to change every time, so we
317 double-check failures */
318 if (!EQ (previous_items[i], AREF (menu_items, i)))
319 if (!(STRINGP (previous_items[i])
320 && STRINGP (AREF (menu_items, i))
321 && !strcmp (SDATA (previous_items[i]),
322 SDATA (AREF (menu_items, i)))))
324 if (i == previous_menu_items_used)
330 t += 1000*tb.time+tb.millitm;
331 fprintf (stderr, "NO CHANGE! CUTTING OUT after %ld msec.\n", t);
334 free_menubar_widget_value_tree (first_wv);
335 discard_menu_items ();
336 unbind_to (specpdl_count, Qnil);
342 /* The menu items are different, so store them in the frame */
343 /* FIXME: this is not correct for single-submenu case */
344 f->menu_bar_vector = menu_items;
345 f->menu_bar_items_used = menu_items_used;
347 /* Calls restore_menu_items, etc., as they were outside */
348 unbind_to (specpdl_count, Qnil);
350 /* Parse stage 2a: now GC cannot happen during the lifetime of the
351 widget_value, so it's safe to store data from a Lisp_String */
352 wv = first_wv->contents;
353 for (i = 0; i < ASIZE (items); i += 4)
356 string = AREF (items, i + 1);
359 /* if (submenu && strcmp (submenuTitle, SDATA (string)))
362 wv->name = SSDATA (string);
363 update_submenu_strings (wv->contents);
367 /* Now, update the NS menu; if we have a submenu, use that, otherwise
368 create a new menu for each sub and fill it. */
371 for (wv = first_wv->contents; wv; wv = wv->next)
373 if (!strcmp (submenuTitle, wv->name))
375 [submenu fillWithWidgetValue: wv->contents];
376 last_submenu = submenu;
383 [menu fillWithWidgetValue: first_wv->contents];
389 static int n_previous_strings = 0;
390 static char previous_strings[100][10];
391 static struct frame *last_f = NULL;
395 wv = xmalloc_widget_value ();
396 wv->name = "menubar";
399 wv->button_type = BUTTON_TYPE_NONE;
403 /* Make widget-value tree w/ just the top level menu bar strings */
404 items = FRAME_MENU_BAR_ITEMS (f);
407 free_menubar_widget_value_tree (first_wv);
414 /* check if no change.. this mechanism is a bit rough, but ready */
415 n = ASIZE (items) / 4;
416 if (f == last_f && n_previous_strings == n)
418 for (i = 0; i<n; i++)
420 string = AREF (items, 4*i+1);
422 if (EQ (string, make_number (0))) // FIXME: Why??? --Stef
425 if (previous_strings[i][0])
429 if (strncmp (previous_strings[i], SDATA (string), 10))
435 free_menubar_widget_value_tree (first_wv);
443 for (i = 0; i < ASIZE (items); i += 4)
445 string = AREF (items, i + 1);
450 strncpy (previous_strings[i/4], SDATA (string), 10);
452 wv = xmalloc_widget_value ();
453 wv->name = SSDATA (string);
456 wv->button_type = BUTTON_TYPE_NONE;
458 wv->call_data = (void *) (EMACS_INT) (-1);
461 /* we'll update the real copy under app menu when time comes */
462 if (!strcmp ("Services", wv->name))
464 /* but we need to make sure it will update on demand */
465 [svcsMenu setFrame: f];
469 [menu addSubmenuWithTitle: wv->name forFrame: f];
474 first_wv->contents = wv;
480 n_previous_strings = n;
482 n_previous_strings = 0;
485 free_menubar_widget_value_tree (first_wv);
490 t += 1000*tb.time+tb.millitm;
491 fprintf (stderr, "Menu update took %ld msec.\n", t);
496 [NSApp setMainMenu: menu];
504 /* Main emacs core entry point for menubar menus: called to indicate that the
505 frame's menus have changed, and the *step representation should be updated
508 set_frame_menubar (struct frame *f, int first_time, int deep_p)
510 ns_update_menubar (f, deep_p, nil);
514 /* ==========================================================================
516 Menu: class implementation
518 ========================================================================== */
521 /* Menu that can define itself from Emacs "widget_value"s and will lazily
522 update itself when user clicked. Based on Carbon/AppKit implementation
523 by Yamamoto Mitsuharu. */
524 @implementation EmacsMenu
526 /* override designated initializer */
527 - initWithTitle: (NSString *)title
529 if (self = [super initWithTitle: title])
530 [self setAutoenablesItems: NO];
535 /* used for top-level */
536 - initWithTitle: (NSString *)title frame: (struct frame *)f
538 [self initWithTitle: title];
541 [self setDelegate: self];
547 - (void)setFrame: (struct frame *)f
553 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
554 extern NSString *NSMenuDidBeginTrackingNotification;
559 -(void)trackingNotification:(NSNotification *)notification
561 /* Update menu in menuNeedsUpdate only while tracking menus. */
562 trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
567 /* delegate method called when a submenu is being opened: run a 'deep' call
568 to set_frame_menubar */
569 - (void)menuNeedsUpdate: (NSMenu *)menu
571 if (!FRAME_LIVE_P (frame))
574 /* Cocoa/Carbon will request update on every keystroke
575 via IsMenuKeyEvent -> CheckMenusForKeyEvent. These are not needed
576 since key equivalents are handled through emacs.
577 On Leopard, even keystroke events generate SystemDefined event.
578 Third-party applications that enhance mouse / trackpad
579 interaction, or also VNC/Remote Desktop will send events
580 of type AppDefined rather than SysDefined.
581 Menus will fail to show up if they haven't been initialized.
582 AppDefined events may lack timing data.
584 Thus, we rely on the didBeginTrackingNotification notification
585 as above to indicate the need for updates.
586 From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
587 key press case, NSMenuPropertyItemImage (e.g.) won't be set.
589 if (trackingMenu == 0
590 /* Also, don't try this if from an event picked up asynchronously,
591 as lots of lisp evaluation happens in ns_update_menubar. */
592 || handling_signal != 0)
594 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
595 ns_update_menubar (frame, 1, self);
599 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
601 if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
602 && FRAME_NS_VIEW (SELECTED_FRAME ()))
603 [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
608 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
609 into an accelerator string. We are only able to display a single character
610 for an accelerator, together with an optional modifier combination. (Under
611 Carbon more control was possible, but in Cocoa multi-char strings passed to
612 NSMenuItem get ignored. For now we try to display a super-single letter
613 combo, and return the others as strings to be appended to the item title.
614 (This is signaled by setting keyEquivModMask to 0 for now.) */
615 -(NSString *)parseKeyEquiv: (const char *)key
617 const char *tpos = key;
618 keyEquivModMask = NSCommandKeyMask;
620 if (!key || !strlen (key))
623 while (*tpos == ' ' || *tpos == '(')
625 if ((*tpos == 's') && (*(tpos+1) == '-'))
627 return [NSString stringWithFormat: @"%c", tpos[2]];
629 keyEquivModMask = 0; /* signal */
630 return [NSString stringWithUTF8String: tpos];
634 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
637 widget_value *wv = (widget_value *)wvptr;
639 if (menu_separator_name_p (wv->name))
641 item = [NSMenuItem separatorItem];
642 [self addItem: item];
646 NSString *title, *keyEq;
647 title = [NSString stringWithUTF8String: wv->name];
649 title = @"< ? >"; /* (get out in the open so we know about it) */
651 keyEq = [self parseKeyEquiv: wv->key];
653 /* OS X just ignores modifier strings longer than one character */
654 if (keyEquivModMask == 0)
655 title = [title stringByAppendingFormat: @" (%@)", keyEq];
658 item = [self addItemWithTitle: (NSString *)title
659 action: @selector (menuDown:)
660 keyEquivalent: keyEq];
661 [item setKeyEquivalentModifierMask: keyEquivModMask];
663 [item setEnabled: wv->enabled];
665 /* Draw radio buttons and tickboxes */
666 if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
667 wv->button_type == BUTTON_TYPE_RADIO))
668 [item setState: NSOnState];
670 [item setState: NSOffState];
672 [item setTag: (NSInteger)wv->call_data];
684 for (n = [self numberOfItems]-1; n >= 0; n--)
686 NSMenuItem *item = [self itemAtIndex: n];
687 NSString *title = [item title];
688 if (([title length] == 0 /* OSX 10.5 */
689 || [ns_app_name isEqualToString: title] /* from 10.6 on */
690 || [@"Apple" isEqualToString: title]) /* older */
691 && ![item isSeparatorItem])
693 [self removeItemAtIndex: n];
698 - (void)fillWithWidgetValue: (void *)wvptr
700 widget_value *wv = (widget_value *)wvptr;
702 /* clear existing contents */
703 [self setMenuChangedMessagesEnabled: NO];
706 /* add new contents */
707 for (; wv != NULL; wv = wv->next)
709 NSMenuItem *item = [self addItemWithWidgetValue: wv];
713 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
715 [self setSubmenu: submenu forItem: item];
716 [submenu fillWithWidgetValue: wv->contents];
718 [item setAction: nil];
722 [self setMenuChangedMessagesEnabled: YES];
723 #ifdef NS_IMPL_GNUSTEP
724 if ([[self window] isVisible])
727 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_2
728 if ([self supermenu] == nil)
735 /* adds an empty submenu and returns it */
736 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
738 NSString *titleStr = [NSString stringWithUTF8String: title];
739 NSMenuItem *item = [self addItemWithTitle: titleStr
740 action: nil /*@selector (menuDown:) */
742 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
743 [self setSubmenu: submenu forItem: item];
748 /* run a menu in popup mode */
749 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
750 keymaps: (int)keymaps
752 EmacsView *view = FRAME_NS_VIEW (f);
756 /* p = [view convertPoint:p fromView: nil]; */
757 p.y = NSHeight ([view frame]) - p.y;
758 e = [[view window] currentEvent];
759 event = [NSEvent mouseEventWithType: NSRightMouseDown
762 timestamp: [e timestamp]
763 windowNumber: [[view window] windowNumber]
765 eventNumber: 0/*[e eventNumber] */
769 context_menu_value = -1;
770 [NSMenu popUpContextMenu: self withEvent: event forView: view];
771 retVal = context_menu_value;
772 context_menu_value = 0;
774 ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
782 /* ==========================================================================
784 Context Menu: implementing functions
786 ========================================================================== */
789 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
790 Lisp_Object title, const char **error)
794 Lisp_Object window, tem, keymap;
795 int specpdl_count = SPECPDL_INDEX ();
796 widget_value *wv, *first_wv = 0;
800 /* now parse stage 2 as in ns_update_menubar */
801 wv = xmalloc_widget_value ();
802 wv->name = "contextmenu";
805 wv->button_type = BUTTON_TYPE_NONE;
810 /* FIXME: a couple of one-line differences prevent reuse */
811 wv = digest_single_submenu (0, menu_items_used, Qnil);
814 widget_value *save_wv = 0, *prev_wv = 0;
815 widget_value **submenu_stack
816 = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
817 /* Lisp_Object *subprefix_stack
818 = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
819 int submenu_depth = 0;
823 /* Loop over all panes and items, filling in the tree. */
825 while (i < menu_items_used)
827 if (EQ (AREF (menu_items, i), Qnil))
829 submenu_stack[submenu_depth++] = save_wv;
835 else if (EQ (AREF (menu_items, i), Qlambda))
838 save_wv = submenu_stack[--submenu_depth];
842 else if (EQ (AREF (menu_items, i), Qt)
843 && submenu_depth != 0)
844 i += MENU_ITEMS_PANE_LENGTH;
845 /* Ignore a nil in the item list.
846 It's meaningful only for dialog boxes. */
847 else if (EQ (AREF (menu_items, i), Qquote))
849 else if (EQ (AREF (menu_items, i), Qt))
851 /* Create a new pane. */
852 Lisp_Object pane_name, prefix;
853 const char *pane_string;
855 pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
856 prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
858 #ifndef HAVE_MULTILINGUAL_MENU
859 if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
861 pane_name = ENCODE_MENU_STRING (pane_name);
862 ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
865 pane_string = (NILP (pane_name)
866 ? "" : SSDATA (pane_name));
867 /* If there is just one top-level pane, put all its items directly
868 under the top-level menu. */
869 if (menu_items_n_panes == 1)
872 /* If the pane has a meaningful name,
873 make the pane a top-level menu item
874 with its items as a submenu beneath it. */
875 if (!keymaps && strcmp (pane_string, ""))
877 wv = xmalloc_widget_value ();
881 first_wv->contents = wv;
882 wv->name = pane_string;
883 if (keymaps && !NILP (prefix))
887 wv->button_type = BUTTON_TYPE_NONE;
898 i += MENU_ITEMS_PANE_LENGTH;
902 /* Create a new item within current pane. */
903 Lisp_Object item_name, enable, descrip, def, type, selected, help;
904 item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
905 enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
906 descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
907 def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
908 type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
909 selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
910 help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
912 #ifndef HAVE_MULTILINGUAL_MENU
913 if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
915 item_name = ENCODE_MENU_STRING (item_name);
916 ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
919 if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
921 descrip = ENCODE_MENU_STRING (descrip);
922 ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
924 #endif /* not HAVE_MULTILINGUAL_MENU */
926 wv = xmalloc_widget_value ();
930 save_wv->contents = wv;
931 wv->name = SSDATA (item_name);
933 wv->key = SSDATA (descrip);
935 /* If this item has a null value,
936 make the call_data null so that it won't display a box
937 when the mouse is on it. */
939 = !NILP (def) ? (void *) &AREF (menu_items, i) : 0;
940 wv->enabled = !NILP (enable);
943 wv->button_type = BUTTON_TYPE_NONE;
944 else if (EQ (type, QCtoggle))
945 wv->button_type = BUTTON_TYPE_TOGGLE;
946 else if (EQ (type, QCradio))
947 wv->button_type = BUTTON_TYPE_RADIO;
951 wv->selected = !NILP (selected);
953 if (! STRINGP (help))
960 i += MENU_ITEMS_ITEM_LENGTH;
968 widget_value *wv_title = xmalloc_widget_value ();
969 widget_value *wv_sep = xmalloc_widget_value ();
971 /* Maybe replace this separator with a bitmap or owner-draw item
972 so that it looks better. Having two separators looks odd. */
974 wv_sep->next = first_wv->contents;
977 #ifndef HAVE_MULTILINGUAL_MENU
978 if (STRING_MULTIBYTE (title))
979 title = ENCODE_MENU_STRING (title);
982 wv_title->name = SSDATA (title);
983 wv_title->enabled = NO;
984 wv_title->button_type = BUTTON_TYPE_NONE;
985 wv_title->help = Qnil;
986 wv_title->next = wv_sep;
987 first_wv->contents = wv_title;
990 pmenu = [[EmacsMenu alloc] initWithTitle:
991 [NSString stringWithUTF8String: SDATA (title)]];
992 [pmenu fillWithWidgetValue: first_wv->contents];
993 free_menubar_widget_value_tree (first_wv);
994 unbind_to (specpdl_count, Qnil);
996 popup_activated_flag = 1;
997 tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
998 popup_activated_flag = 0;
999 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1005 /* ==========================================================================
1007 Toolbar: externally-called functions
1009 ========================================================================== */
1012 free_frame_tool_bar (FRAME_PTR f)
1013 /* --------------------------------------------------------------------------
1014 Under NS we just hide the toolbar until it might be needed again.
1015 -------------------------------------------------------------------------- */
1018 [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1019 FRAME_TOOLBAR_HEIGHT (f) = 0;
1024 update_frame_tool_bar (FRAME_PTR f)
1025 /* --------------------------------------------------------------------------
1026 Update toolbar contents
1027 -------------------------------------------------------------------------- */
1030 EmacsView *view = FRAME_NS_VIEW (f);
1031 NSWindow *window = [view window];
1032 EmacsToolbar *toolbar = [view toolbar];
1035 [toolbar clearActive];
1037 /* update EmacsToolbar as in GtkUtils, build items list */
1038 for (i = 0; i < f->n_tool_bar_items; ++i)
1040 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1041 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1043 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1044 BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1049 Lisp_Object helpObj;
1050 const char *helpText;
1052 /* If image is a vector, choose the image according to the
1054 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1055 if (VECTORP (image))
1057 /* NS toolbar auto-computes disabled and selected images */
1058 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1059 xassert (ASIZE (image) >= idx);
1060 image = AREF (image, idx);
1066 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1068 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1069 helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1071 /* Ignore invalid image specifications. */
1072 if (!valid_image_p (image))
1074 /* Don't log anything, GNUS makes invalid images all the time. */
1078 img_id = lookup_image (f, image);
1079 img = IMAGE_FROM_ID (f, img_id);
1080 prepare_image_for_display (f, img);
1082 if (img->load_failed_p || img->pixmap == nil)
1084 NSLog (@"Could not prepare toolbar image for display.");
1088 [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1089 enabled: enabled_p];
1093 if (![toolbar isVisible])
1094 [toolbar setVisible: YES];
1096 if ([toolbar changed])
1098 /* inform app that toolbar has changed */
1099 NSDictionary *dict = [toolbar configurationDictionary];
1100 NSMutableDictionary *newDict = [dict mutableCopy];
1101 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1103 while ((key = [keys nextObject]) != nil)
1105 NSObject *val = [dict objectForKey: key];
1106 if ([val isKindOfClass: [NSArray class]])
1109 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1114 [toolbar setConfigurationFromDictionary: newDict];
1118 FRAME_TOOLBAR_HEIGHT (f) =
1119 NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1120 - FRAME_NS_TITLEBAR_HEIGHT (f);
1125 /* ==========================================================================
1127 Toolbar: class implementation
1129 ========================================================================== */
1131 @implementation EmacsToolbar
1133 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1135 self = [super initWithIdentifier: identifier];
1137 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1138 [self setSizeMode: NSToolbarSizeModeSmall];
1139 [self setDelegate: self];
1140 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1141 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1142 prevEnablement = enablement = 0L;
1148 [prevIdentifiers release];
1149 [activeIdentifiers release];
1150 [identifierToItem release];
1154 - (void) clearActive
1156 [prevIdentifiers release];
1157 prevIdentifiers = [activeIdentifiers copy];
1158 [activeIdentifiers removeAllObjects];
1159 prevEnablement = enablement;
1165 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1166 enablement == prevEnablement ? NO : YES;
1169 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1170 helpText: (const char *)help enabled: (BOOL)enabled
1172 /* 1) come up w/identifier */
1173 NSString *identifier
1174 = [NSString stringWithFormat: @"%u", [img hash]];
1176 /* 2) create / reuse item */
1177 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1180 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1182 [item setImage: img];
1183 [item setToolTip: [NSString stringWithUTF8String: help]];
1184 [item setTarget: emacsView];
1185 [item setAction: @selector (toolbarClicked:)];
1189 [item setEnabled: enabled];
1191 /* 3) update state */
1192 [identifierToItem setObject: item forKey: identifier];
1193 [activeIdentifiers addObject: identifier];
1194 enablement = (enablement << 1) | (enabled == YES);
1197 /* This overrides super's implementation, which automatically sets
1198 all items to enabled state (for some reason). */
1199 - (void)validateVisibleItems { }
1202 /* delegate methods */
1204 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1205 itemForItemIdentifier: (NSString *)itemIdentifier
1206 willBeInsertedIntoToolbar: (BOOL)flag
1208 /* look up NSToolbarItem by identifier and return... */
1209 return [identifierToItem objectForKey: itemIdentifier];
1212 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1214 /* return entire set.. */
1215 return activeIdentifiers;
1218 /* for configuration palette (not yet supported) */
1219 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1221 /* return entire set... */
1222 return [identifierToItem allKeys];
1225 /* optional and unneeded */
1226 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1227 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1228 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1230 @end /* EmacsToolbar */
1234 /* ==========================================================================
1236 Tooltip: class implementation
1238 ========================================================================== */
1240 /* Needed because NeXTstep does not provide enough control over tooltip
1242 @implementation EmacsTooltip
1246 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1247 blue: 0.792 alpha: 0.95];
1248 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1249 NSFont *sfont = [font screenFont];
1250 int height = [sfont ascender] - [sfont descender];
1251 /*[font boundingRectForFont].size.height; */
1252 NSRect r = NSMakeRect (0, 0, 100, height+6);
1254 textField = [[NSTextField alloc] initWithFrame: r];
1255 [textField setFont: font];
1256 [textField setBackgroundColor: col];
1258 [textField setEditable: NO];
1259 [textField setSelectable: NO];
1260 [textField setBordered: NO];
1261 [textField setBezeled: NO];
1262 [textField setDrawsBackground: YES];
1264 win = [[NSWindow alloc]
1265 initWithContentRect: [textField frame]
1267 backing: NSBackingStoreBuffered
1269 [win setHasShadow: YES];
1270 [win setReleasedWhenClosed: NO];
1271 [win setDelegate: self];
1272 [[win contentView] addSubview: textField];
1273 /* [win setBackgroundColor: col]; */
1274 [win setOpaque: NO];
1283 [textField release];
1287 - (void) setText: (char *)text
1289 NSString *str = [NSString stringWithUTF8String: text];
1290 NSRect r = [textField frame];
1293 [textField setStringValue: str];
1294 tooltipDims = [[textField cell] cellSize];
1296 r.size.width = tooltipDims.width;
1297 r.size.height = tooltipDims.height;
1298 [textField setFrame: r];
1301 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1303 NSRect wr = [win frame];
1305 wr.origin = NSMakePoint (x, y);
1306 wr.size = [textField frame].size;
1308 [win setFrame: wr display: YES];
1309 [win orderFront: self];
1311 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1312 selector: @selector (hide)
1313 userInfo: nil repeats: NO];
1322 if ([timer isValid])
1331 return timer != nil;
1336 return [textField frame];
1339 @end /* EmacsTooltip */
1343 /* ==========================================================================
1345 Popup Dialog: implementing functions
1347 ========================================================================== */
1351 pop_down_menu (Lisp_Object arg)
1353 struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1354 if (popup_activated_flag)
1356 popup_activated_flag = 0;
1358 [NSApp endModalSession: popupSession];
1359 [((EmacsDialogPanel *) (p->pointer)) close];
1360 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1368 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1371 Lisp_Object window, tem, title;
1376 NSTRACE (x-popup-dialog);
1380 isQ = NILP (header);
1382 if (EQ (position, Qt)
1383 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1384 || EQ (XCAR (position), Qtool_bar))))
1386 window = selected_window;
1388 else if (CONSP (position))
1391 tem = Fcar (position);
1392 if (XTYPE (tem) == Lisp_Cons)
1393 window = Fcar (Fcdr (position));
1396 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1397 window = Fcar (tem); /* POSN_WINDOW (tem) */
1400 else if (WINDOWP (position) || FRAMEP (position))
1407 if (FRAMEP (window))
1408 f = XFRAME (window);
1409 else if (WINDOWP (window))
1411 CHECK_LIVE_WINDOW (window);
1412 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1415 CHECK_WINDOW (window);
1417 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1418 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1420 title = Fcar (contents);
1421 CHECK_STRING (title);
1423 if (NILP (Fcar (Fcdr (contents))))
1424 /* No buttons specified, add an "Ok" button so users can pop down
1426 contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1429 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1432 int specpdl_count = SPECPDL_INDEX ();
1433 record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1434 popup_activated_flag = 1;
1435 tem = [dialog runDialogAt: p];
1436 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
1444 /* ==========================================================================
1446 Popup Dialog: class implementation
1448 ========================================================================== */
1450 @interface FlippedView : NSView
1455 @implementation FlippedView
1462 @implementation EmacsDialogPanel
1465 #define ICONSIZE 64.0
1466 #define TEXTHEIGHT 20.0
1467 #define MINCELLWIDTH 90.0
1469 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1470 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1472 NSSize spacing = {SPACER, SPACER};
1474 char this_cmd_name[80];
1476 static NSImageView *imgView;
1477 static FlippedView *contentView;
1482 area.origin.x = 3*SPACER;
1483 area.origin.y = 2*SPACER;
1484 area.size.width = ICONSIZE;
1485 area.size.height= ICONSIZE;
1486 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1487 [img setScalesWhenResized: YES];
1488 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1489 imgView = [[NSImageView alloc] initWithFrame: area];
1490 [imgView setImage: img];
1491 [imgView setEditable: NO];
1495 aStyle = NSTitledWindowMask;
1499 [super initWithContentRect: contentRect styleMask: aStyle
1500 backing: backingType defer: flag];
1501 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1502 [self setContentView: contentView];
1504 [[self contentView] setAutoresizesSubviews: YES];
1506 [[self contentView] addSubview: imgView];
1507 [self setTitle: @""];
1509 area.origin.x += ICONSIZE+2*SPACER;
1510 /* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1511 area.size.width = 400;
1512 area.size.height= TEXTHEIGHT;
1513 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1514 [[self contentView] addSubview: command];
1515 [command setStringValue: ns_app_name];
1516 [command setDrawsBackground: NO];
1517 [command setBezeled: NO];
1518 [command setSelectable: NO];
1519 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1521 /* area.origin.x = ICONSIZE+2*SPACER;
1522 area.origin.y = TEXTHEIGHT + 2*SPACER;
1523 area.size.width = 400;
1524 area.size.height= 2;
1525 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1526 [[self contentView] addSubview: tem];
1527 [tem setTitlePosition: NSNoTitle];
1528 [tem setAutoresizingMask: NSViewWidthSizable];*/
1530 /* area.origin.x = ICONSIZE+2*SPACER; */
1531 area.origin.y += TEXTHEIGHT+SPACER;
1532 area.size.width = 400;
1533 area.size.height= TEXTHEIGHT;
1534 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1535 [[self contentView] addSubview: title];
1536 [title setDrawsBackground: NO];
1537 [title setBezeled: NO];
1538 [title setSelectable: NO];
1539 [title setFont: [NSFont systemFontOfSize: 11.0]];
1541 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1542 [cell setBordered: NO];
1543 [cell setEnabled: NO];
1544 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1545 [cell setBezelStyle: NSRoundedBezelStyle];
1547 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1548 mode: NSHighlightModeMatrix
1551 numberOfColumns: 1];
1552 [[self contentView] addSubview: matrix];
1554 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1555 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1556 [matrix setIntercellSpacing: spacing];
1558 [self setOneShot: YES];
1559 [self setReleasedWhenClosed: YES];
1560 [self setHidesOnDeactivate: YES];
1565 - (BOOL)windowShouldClose: (id)sender
1567 [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1572 void process_dialog (id window, Lisp_Object list)
1577 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1580 if (XTYPE (item) == Lisp_String)
1582 [window addString: SDATA (item) row: row++];
1584 else if (XTYPE (item) == Lisp_Cons)
1586 [window addButton: SDATA (XCAR (item))
1587 value: XCDR (item) row: row++];
1589 else if (NILP (item))
1598 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1607 cell = [matrix cellAtRow: row column: cols-1];
1608 [cell setTarget: self];
1609 [cell setAction: @selector (clicked: )];
1610 [cell setTitle: [NSString stringWithUTF8String: str]];
1611 [cell setTag: XHASH (val)]; // FIXME: BIG UGLY HACK!!
1612 [cell setBordered: YES];
1613 [cell setEnabled: YES];
1619 - addString: (char *)str row: (int)row
1628 cell = [matrix cellAtRow: row column: cols-1];
1629 [cell setTitle: [NSString stringWithUTF8String: str]];
1630 [cell setBordered: YES];
1631 [cell setEnabled: NO];
1647 NSArray *sellist = nil;
1650 sellist = [sender selectedCells];
1651 if ([sellist count]<1)
1654 seltag = [[sellist objectAtIndex: 0] tag];
1655 if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1656 [NSApp stopModalWithCode: seltag];
1661 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1666 if (XTYPE (contents) == Lisp_Cons)
1668 head = Fcar (contents);
1669 process_dialog (self, Fcdr (contents));
1674 if (XTYPE (head) == Lisp_String)
1675 [title setStringValue:
1676 [NSString stringWithUTF8String: SDATA (head)]];
1677 else if (isQ == YES)
1678 [title setStringValue: @"Question"];
1680 [title setStringValue: @"Information"];
1686 if (cols == 1 && rows > 1) /* Never told where to split */
1689 for (i = 0; i<rows/2; i++)
1691 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1692 atRow: i column: 1];
1693 [matrix removeRow: (rows+1)/2];
1699 NSSize csize = [matrix cellSize];
1700 if (csize.width < MINCELLWIDTH)
1702 csize.width = MINCELLWIDTH;
1703 [matrix setCellSize: csize];
1704 [matrix sizeToCells];
1709 [command sizeToFit];
1713 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1715 t.origin.x = r.origin.x;
1716 t.size.width = r.size.width;
1718 r = [command frame];
1719 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1721 t.origin.x = r.origin.x;
1722 t.size.width = r.size.width;
1726 s = [(NSView *)[self contentView] frame];
1727 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1728 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1729 [self setFrame: r display: NO];
1738 { [super dealloc]; return; };
1742 - (Lisp_Object)runDialogAt: (NSPoint)p
1746 /* initiate a session that will be ended by pop_down_menu */
1747 popupSession = [NSApp beginModalSessionForWindow: self];
1748 while (popup_activated_flag
1749 && (ret = [NSApp runModalSession: popupSession])
1750 == NSRunContinuesResponse)
1752 /* Run this for timers.el, indep of atimers; might not return.
1753 TODO: use return value to avoid calling every iteration. */
1755 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1758 { /* FIXME: BIG UGLY HACK!!! */
1760 *(EMACS_INT*)(&tmp) = ret;
1768 /* ==========================================================================
1772 ========================================================================== */
1774 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1775 doc: /* Cause the NS menu to be re-calculated. */)
1778 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1783 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1784 doc: /* Pop up a dialog box and return user's selection.
1785 POSITION specifies which frame to use.
1786 This is normally a mouse button event or a window or frame.
1787 If POSITION is t, it means to use the frame the mouse is on.
1788 The dialog box appears in the middle of the specified frame.
1790 CONTENTS specifies the alternatives to display in the dialog box.
1791 It is a list of the form (DIALOG ITEM1 ITEM2...).
1792 Each ITEM is a cons cell (STRING . VALUE).
1793 The return value is VALUE from the chosen item.
1795 An ITEM may also be just a string--that makes a nonselectable item.
1796 An ITEM may also be nil--that means to put all preceding items
1797 on the left of the dialog box and all following items on the right.
1798 \(By default, approximately half appear on each side.)
1800 If HEADER is non-nil, the frame title for the box is "Information",
1801 otherwise it is "Question".
1803 If the user gets rid of the dialog box without making a valid choice,
1804 for instance using the window manager, then this produces a quit and
1805 `x-popup-dialog' does not return. */)
1806 (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1808 return ns_popup_dialog (position, contents, header);
1811 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1812 doc: /* Return t if a menu or popup dialog is active. */)
1815 return popup_activated () ? Qt : Qnil;
1818 /* ==========================================================================
1820 Lisp interface declaration
1822 ========================================================================== */
1825 syms_of_nsmenu (void)
1827 #ifndef NS_IMPL_COCOA
1828 /* Don't know how to keep track of this in Next/Open/Gnustep. Always
1829 update menus there. */
1832 defsubr (&Sx_popup_dialog);
1833 defsubr (&Sns_reset_menu);
1834 defsubr (&Smenu_or_popup_active_p);
1836 Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1837 staticpro (&Qdebug_on_next_call);