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 /* NOTE: toolbar implementation is at end,
78 following complete menu implementation. */
81 /* ==========================================================================
83 Menu: Externally-called functions
85 ========================================================================== */
88 /* FIXME: not currently used, but should normalize with other terms. */
90 x_activate_menubar (struct frame *f)
92 fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
96 /* Supposed to discard menubar and free storage. Since we share the
97 menubar among frames and update its context for the focused window,
98 there is nothing to do here. */
100 free_frame_menubar (struct frame *f)
107 popup_activated (void)
109 return popup_activated_flag;
113 /* --------------------------------------------------------------------------
114 Update menubar. Three cases:
115 1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
116 just top-level menu strings (OS X), or goto case (2) (GNUstep).
117 2) deep_p = 1, submenu = nil: Recompute all submenus.
118 3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
119 -------------------------------------------------------------------------- */
121 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
123 NSAutoreleasePool *pool;
124 id menu = [NSApp mainMenu];
125 static EmacsMenu *last_submenu = nil;
127 const char *submenuTitle = [[submenu title] UTF8String];
128 extern int waiting_for_input;
131 widget_value *wv, *first_wv, *prev_wv = 0;
139 NSTRACE (set_frame_menubar);
141 if (f != SELECTED_FRAME ())
143 XSETFRAME (Vmenu_updating_frame, f);
144 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
147 pool = [[NSAutoreleasePool alloc] init];
149 /* Menu may have been created automatically; if so, discard it. */
150 if ([menu isKindOfClass: [EmacsMenu class]] == NO)
158 menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
162 { /* close up anything on there */
163 id attMenu = [menu attachedMenu];
170 t = -(1000*tb.time+tb.millitm);
173 #ifdef NS_IMPL_GNUSTEP
174 deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
179 /* Fully parse one or more of the submenus. */
181 int *submenu_start, *submenu_end;
182 int *submenu_top_level_items, *submenu_n_panes;
183 struct buffer *prev = current_buffer;
185 int specpdl_count = SPECPDL_INDEX ();
186 int previous_menu_items_used = f->menu_bar_items_used;
187 Lisp_Object *previous_items
188 = (Lisp_Object *) alloca (previous_menu_items_used
189 * sizeof (Lisp_Object));
191 /* lisp preliminaries */
192 buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
193 specbind (Qinhibit_quit, Qt);
194 specbind (Qdebug_on_next_call, Qnil);
195 record_unwind_save_match_data ();
196 if (NILP (Voverriding_local_map_menu_flag))
198 specbind (Qoverriding_terminal_local_map, Qnil);
199 specbind (Qoverriding_local_map, Qnil);
201 set_buffer_internal_1 (XBUFFER (buffer));
203 /* TODO: for some reason this is not needed in other terms,
204 but some menu updates call Info-extract-pointer which causes
205 abort-on-error if waiting-for-input. Needs further investigation. */
206 owfi = waiting_for_input;
207 waiting_for_input = 0;
209 /* lucid hook and possible reset */
210 safe_run_hooks (Qactivate_menubar_hook);
211 if (! NILP (Vlucid_menu_bar_dirty_flag))
212 call0 (Qrecompute_lucid_menubar);
213 safe_run_hooks (Qmenu_bar_update_hook);
214 FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
216 /* Now ready to go */
217 items = FRAME_MENU_BAR_ITEMS (f);
219 /* Save the frame's previous menu bar contents data */
220 if (previous_menu_items_used)
221 memcpy (previous_items, XVECTOR (f->menu_bar_vector)->contents,
222 previous_menu_items_used * sizeof (Lisp_Object));
224 /* parse stage 1: extract from lisp */
227 menu_items = f->menu_bar_vector;
228 menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
229 submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
230 submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
231 submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
232 submenu_top_level_items
233 = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
235 for (i = 0; i < XVECTOR (items)->size; i += 4)
237 Lisp_Object key, string, maps;
239 key = XVECTOR (items)->contents[i];
240 string = XVECTOR (items)->contents[i + 1];
241 maps = XVECTOR (items)->contents[i + 2];
245 /* FIXME: we'd like to only parse the needed submenu, but this
246 was causing crashes in the _common parsing code.. need to make
247 sure proper initialization done.. */
248 /* if (submenu && strcmp (submenuTitle, SDATA (string)))
251 submenu_start[i] = menu_items_used;
253 menu_items_n_panes = 0;
254 submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
255 submenu_n_panes[i] = menu_items_n_panes;
256 submenu_end[i] = menu_items_used;
260 finish_menu_items ();
261 waiting_for_input = owfi;
264 if (submenu && n == 0)
266 /* should have found a menu for this one but didn't */
267 fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
269 discard_menu_items ();
270 unbind_to (specpdl_count, Qnil);
276 /* parse stage 2: insert into lucid 'widget_value' structures
277 [comments in other terms say not to evaluate lisp code here] */
278 wv = xmalloc_widget_value ();
279 wv->name = "menubar";
282 wv->button_type = BUTTON_TYPE_NONE;
286 for (i = 0; i < 4*n; i += 4)
288 menu_items_n_panes = submenu_n_panes[i];
289 wv = digest_single_submenu (submenu_start[i], submenu_end[i],
290 submenu_top_level_items[i]);
294 first_wv->contents = wv;
295 /* Don't set wv->name here; GC during the loop might relocate it. */
297 wv->button_type = BUTTON_TYPE_NONE;
301 set_buffer_internal_1 (prev);
303 /* Compare the new menu items with previous, and leave off if no change */
304 /* FIXME: following other terms here, but seems like this should be
305 done before parse stage 2 above, since its results aren't used */
306 if (previous_menu_items_used
307 && (!submenu || (submenu && submenu == last_submenu))
308 && menu_items_used == previous_menu_items_used)
310 for (i = 0; i < previous_menu_items_used; i++)
311 /* FIXME: this ALWAYS fails on Buffers menu items.. something
312 about their strings causes them to change every time, so we
313 double-check failures */
314 if (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i]))
315 if (!(STRINGP (previous_items[i])
316 && STRINGP (XVECTOR (menu_items)->contents[i])
317 && !strcmp (SDATA (previous_items[i]),
318 SDATA (XVECTOR (menu_items)->contents[i]))))
320 if (i == previous_menu_items_used)
326 t += 1000*tb.time+tb.millitm;
327 fprintf (stderr, "NO CHANGE! CUTTING OUT after %ld msec.\n", t);
330 free_menubar_widget_value_tree (first_wv);
331 discard_menu_items ();
332 unbind_to (specpdl_count, Qnil);
338 /* The menu items are different, so store them in the frame */
339 /* FIXME: this is not correct for single-submenu case */
340 f->menu_bar_vector = menu_items;
341 f->menu_bar_items_used = menu_items_used;
343 /* Calls restore_menu_items, etc., as they were outside */
344 unbind_to (specpdl_count, Qnil);
346 /* Parse stage 2a: now GC cannot happen during the lifetime of the
347 widget_value, so it's safe to store data from a Lisp_String */
348 wv = first_wv->contents;
349 for (i = 0; i < XVECTOR (items)->size; i += 4)
352 string = XVECTOR (items)->contents[i + 1];
355 /* if (submenu && strcmp (submenuTitle, SDATA (string)))
358 wv->name = SSDATA (string);
359 update_submenu_strings (wv->contents);
363 /* Now, update the NS menu; if we have a submenu, use that, otherwise
364 create a new menu for each sub and fill it. */
367 for (wv = first_wv->contents; wv; wv = wv->next)
369 if (!strcmp (submenuTitle, wv->name))
371 [submenu fillWithWidgetValue: wv->contents];
372 last_submenu = submenu;
379 [menu fillWithWidgetValue: first_wv->contents];
385 static int n_previous_strings = 0;
386 static char previous_strings[100][10];
387 static struct frame *last_f = NULL;
391 wv = xmalloc_widget_value ();
392 wv->name = "menubar";
395 wv->button_type = BUTTON_TYPE_NONE;
399 /* Make widget-value tree w/ just the top level menu bar strings */
400 items = FRAME_MENU_BAR_ITEMS (f);
409 /* check if no change.. this mechanism is a bit rough, but ready */
410 n = XVECTOR (items)->size / 4;
411 if (f == last_f && n_previous_strings == n)
413 for (i = 0; i<n; i++)
415 string = AREF (items, 4*i+1);
417 if (EQ (string, make_number (0))) // FIXME: Why??? --Stef
420 if (previous_strings[i][0])
424 if (strncmp (previous_strings[i], SDATA (string), 10))
437 for (i = 0; i < XVECTOR (items)->size; i += 4)
439 string = XVECTOR (items)->contents[i + 1];
444 strncpy (previous_strings[i/4], SDATA (string), 10);
446 wv = xmalloc_widget_value ();
447 wv->name = SSDATA (string);
450 wv->button_type = BUTTON_TYPE_NONE;
452 wv->call_data = (void *) (EMACS_INT) (-1);
455 /* we'll update the real copy under app menu when time comes */
456 if (!strcmp ("Services", wv->name))
458 /* but we need to make sure it will update on demand */
459 [svcsMenu setFrame: f];
460 [svcsMenu setDelegate: svcsMenu];
464 [menu addSubmenuWithTitle: wv->name forFrame: f];
469 first_wv->contents = wv;
475 n_previous_strings = n;
477 n_previous_strings = 0;
480 free_menubar_widget_value_tree (first_wv);
485 t += 1000*tb.time+tb.millitm;
486 fprintf (stderr, "Menu update took %ld msec.\n", t);
491 [NSApp setMainMenu: menu];
499 /* Main emacs core entry point for menubar menus: called to indicate that the
500 frame's menus have changed, and the *step representation should be updated
503 set_frame_menubar (struct frame *f, int first_time, int deep_p)
505 ns_update_menubar (f, deep_p, nil);
509 /* ==========================================================================
511 Menu: class implementation
513 ========================================================================== */
516 /* Menu that can define itself from Emacs "widget_value"s and will lazily
517 update itself when user clicked. Based on Carbon/AppKit implementation
518 by Yamamoto Mitsuharu. */
519 @implementation EmacsMenu
521 /* override designated initializer */
522 - initWithTitle: (NSString *)title
524 if (self = [super initWithTitle: title])
525 [self setAutoenablesItems: NO];
530 /* used for top-level */
531 - initWithTitle: (NSString *)title frame: (struct frame *)f
533 [self initWithTitle: title];
536 [self setDelegate: self];
542 - (void)setFrame: (struct frame *)f
548 /* delegate method called when a submenu is being opened: run a 'deep' call
549 to set_frame_menubar */
550 - (void)menuNeedsUpdate: (NSMenu *)menu
553 if (!FRAME_LIVE_P (frame))
555 event = [[FRAME_NS_VIEW (frame) window] currentEvent];
556 /* HACK: Cocoa/Carbon will request update on every keystroke
557 via IsMenuKeyEvent -> CheckMenusForKeyEvent. These are not needed
558 since key equivalents are handled through emacs.
559 On Leopard, even keystroke events generate SystemDefined events, but
560 their subtype is 8. */
561 if ([event type] != NSSystemDefined || [event subtype] == 8
562 /* Also, don't try this if from an event picked up asynchronously,
563 as lots of lisp evaluation happens in ns_update_menubar. */
564 || handling_signal != 0)
566 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
567 ns_update_menubar (frame, 1, self);
571 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
573 if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
574 && FRAME_NS_VIEW (SELECTED_FRAME ()))
575 [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
580 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
581 into an accelerator string. We are only able to display a single character
582 for an accelerator, together with an optional modifier combination. (Under
583 Carbon more control was possible, but in Cocoa multi-char strings passed to
584 NSMenuItem get ignored. For now we try to display a super-single letter
585 combo, and return the others as strings to be appended to the item title.
586 (This is signaled by setting keyEquivModMask to 0 for now.) */
587 -(NSString *)parseKeyEquiv: (const char *)key
589 const char *tpos = key;
590 keyEquivModMask = NSCommandKeyMask;
592 if (!key || !strlen (key))
595 while (*tpos == ' ' || *tpos == '(')
597 if ((*tpos == 's') && (*(tpos+1) == '-'))
599 return [NSString stringWithFormat: @"%c", tpos[2]];
601 keyEquivModMask = 0; /* signal */
602 return [NSString stringWithUTF8String: tpos];
606 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
609 widget_value *wv = (widget_value *)wvptr;
611 if (menu_separator_name_p (wv->name))
613 item = [NSMenuItem separatorItem];
614 [self addItem: item];
618 NSString *title, *keyEq;
619 title = [NSString stringWithUTF8String: wv->name];
621 title = @"< ? >"; /* (get out in the open so we know about it) */
623 keyEq = [self parseKeyEquiv: wv->key];
625 /* OS X just ignores modifier strings longer than one character */
626 if (keyEquivModMask == 0)
627 title = [title stringByAppendingFormat: @" (%@)", keyEq];
630 item = [self addItemWithTitle: (NSString *)title
631 action: @selector (menuDown:)
632 keyEquivalent: keyEq];
633 [item setKeyEquivalentModifierMask: keyEquivModMask];
635 [item setEnabled: wv->enabled];
637 /* Draw radio buttons and tickboxes */
638 if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
639 wv->button_type == BUTTON_TYPE_RADIO))
640 [item setState: NSOnState];
642 [item setState: NSOffState];
644 [item setTag: (NSInteger)wv->call_data];
656 for (n = [self numberOfItems]-1; n >= 0; n--)
658 NSMenuItem *item = [self itemAtIndex: n];
659 NSString *title = [item title];
660 if (([title length] == 0 /* OSX 10.5 */
661 || [ns_app_name isEqualToString: title] /* from 10.6 on */
662 || [@"Apple" isEqualToString: title]) /* older */
663 && ![item isSeparatorItem])
665 [self removeItemAtIndex: n];
670 - (void)fillWithWidgetValue: (void *)wvptr
672 widget_value *wv = (widget_value *)wvptr;
674 /* clear existing contents */
675 [self setMenuChangedMessagesEnabled: NO];
678 /* add new contents */
679 for (; wv != NULL; wv = wv->next)
681 NSMenuItem *item = [self addItemWithWidgetValue: wv];
685 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
687 [self setSubmenu: submenu forItem: item];
688 [submenu fillWithWidgetValue: wv->contents];
690 [item setAction: nil];
694 [self setMenuChangedMessagesEnabled: YES];
695 #ifdef NS_IMPL_GNUSTEP
696 if ([[self window] isVisible])
699 if ([self supermenu] == nil)
705 /* adds an empty submenu and returns it */
706 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
708 NSString *titleStr = [NSString stringWithUTF8String: title];
709 NSMenuItem *item = [self addItemWithTitle: titleStr
710 action: nil /*@selector (menuDown:) */
712 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
713 [self setSubmenu: submenu forItem: item];
718 /* run a menu in popup mode */
719 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
720 keymaps: (int)keymaps
722 EmacsView *view = FRAME_NS_VIEW (f);
726 /* p = [view convertPoint:p fromView: nil]; */
727 p.y = NSHeight ([view frame]) - p.y;
728 e = [[view window] currentEvent];
729 event = [NSEvent mouseEventWithType: NSRightMouseDown
732 timestamp: [e timestamp]
733 windowNumber: [[view window] windowNumber]
735 eventNumber: 0/*[e eventNumber] */
739 context_menu_value = -1;
740 [NSMenu popUpContextMenu: self withEvent: event forView: view];
741 retVal = context_menu_value;
742 context_menu_value = 0;
744 ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
752 /* ==========================================================================
754 Context Menu: implementing functions
756 ========================================================================== */
759 ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
760 Lisp_Object title, const char **error)
764 Lisp_Object window, tem, keymap;
765 int specpdl_count = SPECPDL_INDEX ();
766 widget_value *wv, *first_wv = 0;
770 /* now parse stage 2 as in ns_update_menubar */
771 wv = xmalloc_widget_value ();
772 wv->name = "contextmenu";
775 wv->button_type = BUTTON_TYPE_NONE;
780 /* FIXME: a couple of one-line differences prevent reuse */
781 wv = digest_single_submenu (0, menu_items_used, Qnil);
784 widget_value *save_wv = 0, *prev_wv = 0;
785 widget_value **submenu_stack
786 = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
787 /* Lisp_Object *subprefix_stack
788 = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
789 int submenu_depth = 0;
793 /* Loop over all panes and items, filling in the tree. */
795 while (i < menu_items_used)
797 if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
799 submenu_stack[submenu_depth++] = save_wv;
805 else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
808 save_wv = submenu_stack[--submenu_depth];
812 else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
813 && submenu_depth != 0)
814 i += MENU_ITEMS_PANE_LENGTH;
815 /* Ignore a nil in the item list.
816 It's meaningful only for dialog boxes. */
817 else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
819 else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
821 /* Create a new pane. */
822 Lisp_Object pane_name, prefix;
823 const char *pane_string;
825 pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
826 prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
828 #ifndef HAVE_MULTILINGUAL_MENU
829 if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
831 pane_name = ENCODE_MENU_STRING (pane_name);
832 ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
835 pane_string = (NILP (pane_name)
836 ? "" : SSDATA (pane_name));
837 /* If there is just one top-level pane, put all its items directly
838 under the top-level menu. */
839 if (menu_items_n_panes == 1)
842 /* If the pane has a meaningful name,
843 make the pane a top-level menu item
844 with its items as a submenu beneath it. */
845 if (!keymaps && strcmp (pane_string, ""))
847 wv = xmalloc_widget_value ();
851 first_wv->contents = wv;
852 wv->name = pane_string;
853 if (keymaps && !NILP (prefix))
857 wv->button_type = BUTTON_TYPE_NONE;
868 i += MENU_ITEMS_PANE_LENGTH;
872 /* Create a new item within current pane. */
873 Lisp_Object item_name, enable, descrip, def, type, selected, help;
874 item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
875 enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
876 descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
877 def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
878 type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
879 selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
880 help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
882 #ifndef HAVE_MULTILINGUAL_MENU
883 if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
885 item_name = ENCODE_MENU_STRING (item_name);
886 ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
889 if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
891 descrip = ENCODE_MENU_STRING (descrip);
892 ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
894 #endif /* not HAVE_MULTILINGUAL_MENU */
896 wv = xmalloc_widget_value ();
900 save_wv->contents = wv;
901 wv->name = SSDATA (item_name);
903 wv->key = SSDATA (descrip);
905 /* If this item has a null value,
906 make the call_data null so that it won't display a box
907 when the mouse is on it. */
909 = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
910 wv->enabled = !NILP (enable);
913 wv->button_type = BUTTON_TYPE_NONE;
914 else if (EQ (type, QCtoggle))
915 wv->button_type = BUTTON_TYPE_TOGGLE;
916 else if (EQ (type, QCradio))
917 wv->button_type = BUTTON_TYPE_RADIO;
921 wv->selected = !NILP (selected);
923 if (! STRINGP (help))
930 i += MENU_ITEMS_ITEM_LENGTH;
938 widget_value *wv_title = xmalloc_widget_value ();
939 widget_value *wv_sep = xmalloc_widget_value ();
941 /* Maybe replace this separator with a bitmap or owner-draw item
942 so that it looks better. Having two separators looks odd. */
944 wv_sep->next = first_wv->contents;
947 #ifndef HAVE_MULTILINGUAL_MENU
948 if (STRING_MULTIBYTE (title))
949 title = ENCODE_MENU_STRING (title);
952 wv_title->name = SSDATA (title);
953 wv_title->enabled = NO;
954 wv_title->button_type = BUTTON_TYPE_NONE;
955 wv_title->help = Qnil;
956 wv_title->next = wv_sep;
957 first_wv->contents = wv_title;
960 pmenu = [[EmacsMenu alloc] initWithTitle:
961 [NSString stringWithUTF8String: SDATA (title)]];
962 [pmenu fillWithWidgetValue: first_wv->contents];
963 free_menubar_widget_value_tree (first_wv);
964 unbind_to (specpdl_count, Qnil);
966 popup_activated_flag = 1;
967 tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
968 popup_activated_flag = 0;
969 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
975 /* ==========================================================================
977 Toolbar: externally-called functions
979 ========================================================================== */
982 free_frame_tool_bar (FRAME_PTR f)
983 /* --------------------------------------------------------------------------
984 Under NS we just hide the toolbar until it might be needed again.
985 -------------------------------------------------------------------------- */
988 [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
989 FRAME_TOOLBAR_HEIGHT (f) = 0;
994 update_frame_tool_bar (FRAME_PTR f)
995 /* --------------------------------------------------------------------------
996 Update toolbar contents
997 -------------------------------------------------------------------------- */
1000 EmacsView *view = FRAME_NS_VIEW (f);
1001 NSWindow *window = [view window];
1002 EmacsToolbar *toolbar = [view toolbar];
1005 [toolbar clearActive];
1007 /* update EmacsToolbar as in GtkUtils, build items list */
1008 for (i = 0; i < f->n_tool_bar_items; ++i)
1010 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1011 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1013 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1014 BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1019 Lisp_Object helpObj;
1020 const char *helpText;
1022 /* If image is a vector, choose the image according to the
1024 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1025 if (VECTORP (image))
1027 /* NS toolbar auto-computes disabled and selected images */
1028 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1029 xassert (ASIZE (image) >= idx);
1030 image = AREF (image, idx);
1036 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1038 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1039 helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1041 /* Ignore invalid image specifications. */
1042 if (!valid_image_p (image))
1044 /* Don't log anything, GNUS makes invalid images all the time. */
1048 img_id = lookup_image (f, image);
1049 img = IMAGE_FROM_ID (f, img_id);
1050 prepare_image_for_display (f, img);
1052 if (img->load_failed_p || img->pixmap == nil)
1054 NSLog (@"Could not prepare toolbar image for display.");
1058 [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1059 enabled: enabled_p];
1063 if (![toolbar isVisible])
1064 [toolbar setVisible: YES];
1066 if ([toolbar changed])
1068 /* inform app that toolbar has changed */
1069 NSDictionary *dict = [toolbar configurationDictionary];
1070 NSMutableDictionary *newDict = [dict mutableCopy];
1071 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1073 while ((key = [keys nextObject]) != nil)
1075 NSObject *val = [dict objectForKey: key];
1076 if ([val isKindOfClass: [NSArray class]])
1079 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1084 [toolbar setConfigurationFromDictionary: newDict];
1088 FRAME_TOOLBAR_HEIGHT (f) =
1089 NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1090 - FRAME_NS_TITLEBAR_HEIGHT (f);
1095 /* ==========================================================================
1097 Toolbar: class implementation
1099 ========================================================================== */
1101 @implementation EmacsToolbar
1103 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1105 self = [super initWithIdentifier: identifier];
1107 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1108 [self setSizeMode: NSToolbarSizeModeSmall];
1109 [self setDelegate: self];
1110 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1111 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1112 prevEnablement = enablement = 0L;
1118 [prevIdentifiers release];
1119 [activeIdentifiers release];
1120 [identifierToItem release];
1124 - (void) clearActive
1126 [prevIdentifiers release];
1127 prevIdentifiers = [activeIdentifiers copy];
1128 [activeIdentifiers removeAllObjects];
1129 prevEnablement = enablement;
1135 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1136 enablement == prevEnablement ? NO : YES;
1139 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1140 helpText: (const char *)help enabled: (BOOL)enabled
1142 /* 1) come up w/identifier */
1143 NSString *identifier
1144 = [NSString stringWithFormat: @"%u", [img hash]];
1146 /* 2) create / reuse item */
1147 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1150 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1152 [item setImage: img];
1153 [item setToolTip: [NSString stringWithUTF8String: help]];
1154 [item setTarget: emacsView];
1155 [item setAction: @selector (toolbarClicked:)];
1159 [item setEnabled: enabled];
1161 /* 3) update state */
1162 [identifierToItem setObject: item forKey: identifier];
1163 [activeIdentifiers addObject: identifier];
1164 enablement = (enablement << 1) | (enabled == YES);
1167 /* This overrides super's implementation, which automatically sets
1168 all items to enabled state (for some reason). */
1169 - (void)validateVisibleItems { }
1172 /* delegate methods */
1174 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1175 itemForItemIdentifier: (NSString *)itemIdentifier
1176 willBeInsertedIntoToolbar: (BOOL)flag
1178 /* look up NSToolbarItem by identifier and return... */
1179 return [identifierToItem objectForKey: itemIdentifier];
1182 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1184 /* return entire set.. */
1185 return activeIdentifiers;
1188 /* for configuration palette (not yet supported) */
1189 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1191 /* return entire set... */
1192 return [identifierToItem allKeys];
1195 /* optional and unneeded */
1196 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1197 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1198 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1200 @end /* EmacsToolbar */
1204 /* ==========================================================================
1206 Tooltip: class implementation
1208 ========================================================================== */
1210 /* Needed because NeXTstep does not provide enough control over tooltip
1212 @implementation EmacsTooltip
1216 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1217 blue: 0.792 alpha: 0.95];
1218 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1219 NSFont *sfont = [font screenFont];
1220 int height = [sfont ascender] - [sfont descender];
1221 /*[font boundingRectForFont].size.height; */
1222 NSRect r = NSMakeRect (0, 0, 100, height+6);
1224 textField = [[NSTextField alloc] initWithFrame: r];
1225 [textField setFont: font];
1226 [textField setBackgroundColor: col];
1228 [textField setEditable: NO];
1229 [textField setSelectable: NO];
1230 [textField setBordered: YES];
1231 [textField setBezeled: YES];
1232 [textField setDrawsBackground: YES];
1234 win = [[NSWindow alloc]
1235 initWithContentRect: [textField frame]
1237 backing: NSBackingStoreBuffered
1239 [win setReleasedWhenClosed: NO];
1240 [win setDelegate: self];
1241 [[win contentView] addSubview: textField];
1242 /* [win setBackgroundColor: col]; */
1243 [win setOpaque: NO];
1252 [textField release];
1256 - (void) setText: (char *)text
1258 NSString *str = [NSString stringWithUTF8String: text];
1259 NSRect r = [textField frame];
1260 NSSize textSize = [str sizeWithAttributes:
1261 [NSDictionary dictionaryWithObject: [[textField font] screenFont]
1262 forKey: NSFontAttributeName]];
1263 NSSize padSize = [[[textField font] screenFont]
1264 boundingRectForFont].size;
1266 r.size.width = textSize.width + padSize.width/2;
1267 r.size.height = textSize.height + padSize.height/2;
1268 [textField setFrame: r];
1269 [textField setStringValue: str];
1272 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1274 NSRect wr = [win frame];
1276 wr.origin = NSMakePoint (x, y);
1277 wr.size = [textField frame].size;
1279 [win setFrame: wr display: YES];
1280 [win orderFront: self];
1282 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1283 selector: @selector (hide)
1284 userInfo: nil repeats: NO];
1293 if ([timer isValid])
1302 return timer != nil;
1307 return [textField frame];
1310 @end /* EmacsTooltip */
1314 /* ==========================================================================
1316 Popup Dialog: implementing functions
1318 ========================================================================== */
1322 pop_down_menu (Lisp_Object arg)
1324 struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1325 if (popup_activated_flag)
1327 popup_activated_flag = 0;
1329 [NSApp endModalSession: popupSession];
1330 [((EmacsDialogPanel *) (p->pointer)) close];
1331 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1339 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1342 Lisp_Object window, tem;
1347 NSTRACE (x-popup-dialog);
1351 isQ = NILP (header);
1353 if (EQ (position, Qt)
1354 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1355 || EQ (XCAR (position), Qtool_bar))))
1357 window = selected_window;
1359 else if (CONSP (position))
1362 tem = Fcar (position);
1363 if (XTYPE (tem) == Lisp_Cons)
1364 window = Fcar (Fcdr (position));
1367 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1368 window = Fcar (tem); /* POSN_WINDOW (tem) */
1371 else if (WINDOWP (position) || FRAMEP (position))
1378 if (FRAMEP (window))
1379 f = XFRAME (window);
1380 else if (WINDOWP (window))
1382 CHECK_LIVE_WINDOW (window);
1383 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1386 CHECK_WINDOW (window);
1388 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1389 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1392 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1395 int specpdl_count = SPECPDL_INDEX ();
1396 record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1397 popup_activated_flag = 1;
1398 tem = [dialog runDialogAt: p];
1399 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
1407 /* ==========================================================================
1409 Popup Dialog: class implementation
1411 ========================================================================== */
1413 @interface FlippedView : NSView
1418 @implementation FlippedView
1425 @implementation EmacsDialogPanel
1428 #define ICONSIZE 64.0
1429 #define TEXTHEIGHT 20.0
1430 #define MINCELLWIDTH 90.0
1432 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1433 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1435 NSSize spacing = {SPACER, SPACER};
1437 char this_cmd_name[80];
1439 static NSImageView *imgView;
1440 static FlippedView *contentView;
1445 area.origin.x = 3*SPACER;
1446 area.origin.y = 2*SPACER;
1447 area.size.width = ICONSIZE;
1448 area.size.height= ICONSIZE;
1449 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1450 [img setScalesWhenResized: YES];
1451 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1452 imgView = [[NSImageView alloc] initWithFrame: area];
1453 [imgView setImage: img];
1454 [imgView setEditable: NO];
1458 aStyle = NSTitledWindowMask;
1462 [super initWithContentRect: contentRect styleMask: aStyle
1463 backing: backingType defer: flag];
1464 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1465 [self setContentView: contentView];
1467 [[self contentView] setAutoresizesSubviews: YES];
1469 [[self contentView] addSubview: imgView];
1470 [self setTitle: @""];
1472 area.origin.x += ICONSIZE+2*SPACER;
1473 /* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1474 area.size.width = 400;
1475 area.size.height= TEXTHEIGHT;
1476 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1477 [[self contentView] addSubview: command];
1478 [command setStringValue: ns_app_name];
1479 [command setDrawsBackground: NO];
1480 [command setBezeled: NO];
1481 [command setSelectable: NO];
1482 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1484 /* area.origin.x = ICONSIZE+2*SPACER;
1485 area.origin.y = TEXTHEIGHT + 2*SPACER;
1486 area.size.width = 400;
1487 area.size.height= 2;
1488 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1489 [[self contentView] addSubview: tem];
1490 [tem setTitlePosition: NSNoTitle];
1491 [tem setAutoresizingMask: NSViewWidthSizable];*/
1493 /* area.origin.x = ICONSIZE+2*SPACER; */
1494 area.origin.y += TEXTHEIGHT+SPACER;
1495 area.size.width = 400;
1496 area.size.height= TEXTHEIGHT;
1497 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1498 [[self contentView] addSubview: title];
1499 [title setDrawsBackground: NO];
1500 [title setBezeled: NO];
1501 [title setSelectable: NO];
1502 [title setFont: [NSFont systemFontOfSize: 11.0]];
1504 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1505 [cell setBordered: NO];
1506 [cell setEnabled: NO];
1507 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1508 [cell setBezelStyle: NSRoundedBezelStyle];
1510 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1511 mode: NSHighlightModeMatrix
1514 numberOfColumns: 1];
1515 [[self contentView] addSubview: matrix];
1517 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1518 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1519 [matrix setIntercellSpacing: spacing];
1521 [self setOneShot: YES];
1522 [self setReleasedWhenClosed: YES];
1523 [self setHidesOnDeactivate: YES];
1528 - (BOOL)windowShouldClose: (id)sender
1530 [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1535 void process_dialog (id window, Lisp_Object list)
1540 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1543 if (XTYPE (item) == Lisp_String)
1545 [window addString: SDATA (item) row: row++];
1547 else if (XTYPE (item) == Lisp_Cons)
1549 [window addButton: SDATA (XCAR (item))
1550 value: XCDR (item) row: row++];
1552 else if (NILP (item))
1561 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1570 cell = [matrix cellAtRow: row column: cols-1];
1571 [cell setTarget: self];
1572 [cell setAction: @selector (clicked: )];
1573 [cell setTitle: [NSString stringWithUTF8String: str]];
1574 [cell setTag: XHASH (val)]; // FIXME: BIG UGLY HACK!!
1575 [cell setBordered: YES];
1576 [cell setEnabled: YES];
1582 - addString: (char *)str row: (int)row
1591 cell = [matrix cellAtRow: row column: cols-1];
1592 [cell setTitle: [NSString stringWithUTF8String: str]];
1593 [cell setBordered: YES];
1594 [cell setEnabled: NO];
1610 NSArray *sellist = nil;
1613 sellist = [sender selectedCells];
1614 if ([sellist count]<1)
1617 seltag = [[sellist objectAtIndex: 0] tag];
1618 if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1619 [NSApp stopModalWithCode: seltag];
1624 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1629 if (XTYPE (contents) == Lisp_Cons)
1631 head = Fcar (contents);
1632 process_dialog (self, Fcdr (contents));
1637 if (XTYPE (head) == Lisp_String)
1638 [title setStringValue:
1639 [NSString stringWithUTF8String: SDATA (head)]];
1640 else if (isQ == YES)
1641 [title setStringValue: @"Question"];
1643 [title setStringValue: @"Information"];
1649 if (cols == 1 && rows > 1) /* Never told where to split */
1652 for (i = 0; i<rows/2; i++)
1654 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1655 atRow: i column: 1];
1656 [matrix removeRow: (rows+1)/2];
1662 NSSize csize = [matrix cellSize];
1663 if (csize.width < MINCELLWIDTH)
1665 csize.width = MINCELLWIDTH;
1666 [matrix setCellSize: csize];
1667 [matrix sizeToCells];
1672 [command sizeToFit];
1676 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1678 t.origin.x = r.origin.x;
1679 t.size.width = r.size.width;
1681 r = [command frame];
1682 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1684 t.origin.x = r.origin.x;
1685 t.size.width = r.size.width;
1689 s = [(NSView *)[self contentView] frame];
1690 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1691 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1692 [self setFrame: r display: NO];
1701 { [super dealloc]; return; };
1705 - (Lisp_Object)runDialogAt: (NSPoint)p
1708 extern EMACS_TIME timer_check (int do_it_now); /* TODO: add to a header */
1710 /* initiate a session that will be ended by pop_down_menu */
1711 popupSession = [NSApp beginModalSessionForWindow: self];
1712 while (popup_activated_flag
1713 && (ret = [NSApp runModalSession: popupSession])
1714 == NSRunContinuesResponse)
1716 /* Run this for timers.el, indep of atimers; might not return.
1717 TODO: use return value to avoid calling every iteration. */
1719 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1722 { /* FIXME: BIG UGLY HACK!!! */
1724 *(EMACS_INT*)(&tmp) = ret;
1732 /* ==========================================================================
1736 ========================================================================== */
1738 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1739 doc: /* Cause the NS menu to be re-calculated. */)
1742 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1747 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1748 doc: /* Pop up a dialog box and return user's selection.
1749 POSITION specifies which frame to use.
1750 This is normally a mouse button event or a window or frame.
1751 If POSITION is t, it means to use the frame the mouse is on.
1752 The dialog box appears in the middle of the specified frame.
1754 CONTENTS specifies the alternatives to display in the dialog box.
1755 It is a list of the form (DIALOG ITEM1 ITEM2...).
1756 Each ITEM is a cons cell (STRING . VALUE).
1757 The return value is VALUE from the chosen item.
1759 An ITEM may also be just a string--that makes a nonselectable item.
1760 An ITEM may also be nil--that means to put all preceding items
1761 on the left of the dialog box and all following items on the right.
1762 \(By default, approximately half appear on each side.)
1764 If HEADER is non-nil, the frame title for the box is "Information",
1765 otherwise it is "Question".
1767 If the user gets rid of the dialog box without making a valid choice,
1768 for instance using the window manager, then this produces a quit and
1769 `x-popup-dialog' does not return. */)
1770 (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1772 return ns_popup_dialog (position, contents, header);
1775 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1776 doc: /* Return t if a menu or popup dialog is active. */)
1779 return popup_activated () ? Qt : Qnil;
1782 /* ==========================================================================
1784 Lisp interface declaration
1786 ========================================================================== */
1789 syms_of_nsmenu (void)
1791 defsubr (&Sx_popup_dialog);
1792 defsubr (&Sns_reset_menu);
1793 defsubr (&Smenu_or_popup_active_p);
1795 Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1796 staticpro (&Qdebug_on_next_call);