(Session Commands): Fix typo.
[bpt/emacs.git] / src / nsmenu.m
CommitLineData
edfda783 1/* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
76b6f707 2 Copyright (C) 2007, 2008, 2009 Free Software Foundation, Inc.
edfda783
AR
3
4This file is part of GNU Emacs.
5
32d235f8 6GNU Emacs is free software: you can redistribute it and/or modify
edfda783 7it under the terms of the GNU General Public License as published by
32d235f8
GM
8the Free Software Foundation, either version 3 of the License, or
9(at your option) any later version.
edfda783
AR
10
11GNU Emacs is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
32d235f8 17along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
edfda783 18
32d235f8 19/*
edfda783
AR
20By Adrian Robert, based on code from original nsmenu.m (Carl Edman,
21Christian Limpach, Scott Bender, Christophe de Dinechin) and code in the
22Carbon version by Yamamoto Mitsuharu. */
23
5a06864f
AR
24/* This should be the first include, as it may set up #defines affecting
25 interpretation of even the system includes. */
edfda783 26#include "config.h"
5a06864f 27
edfda783
AR
28#include "lisp.h"
29#include "window.h"
30#include "buffer.h"
31#include "keymap.h"
32#include "coding.h"
33#include "commands.h"
34#include "blockinput.h"
35#include "nsterm.h"
36#include "termhooks.h"
37#include "keyboard.h"
38
cbe0b5bf
AR
39#define NSMENUPROFILE 0
40
41#if NSMENUPROFILE
edfda783
AR
42#include <sys/timeb.h>
43#include <sys/types.h>
cbe0b5bf 44#endif
edfda783
AR
45
46#define MenuStagger 10.0
47
48#if 0
49int menu_trace_num = 0;
50#define NSTRACE(x) fprintf (stderr, "%s:%d: [%d] " #x "\n", \
51 __FILE__, __LINE__, ++menu_trace_num)
52#else
53#define NSTRACE(x)
54#endif
55
56#if 0
57/* Include lisp -> C common menu parsing code */
58#define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
59#include "nsmenu_common.c"
60#endif
61
edfda783
AR
62extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
63extern Lisp_Object QCtoggle, QCradio;
64
65extern Lisp_Object Vmenu_updating_frame;
66
67Lisp_Object Qdebug_on_next_call;
68extern Lisp_Object Voverriding_local_map, Voverriding_local_map_menu_flag,
69 Qoverriding_local_map, Qoverriding_terminal_local_map;
70
71extern long context_menu_value;
4e622592 72EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
edfda783 73
07b87a10
AR
74/* Nonzero means a menu is currently active. */
75static int popup_activated_flag;
3175b12a 76static NSModalSession popupSession;
07b87a10 77
edfda783
AR
78/* NOTE: toolbar implementation is at end,
79 following complete menu implementation. */
80
81
82/* ==========================================================================
83
84 Menu: Externally-called functions
85
86 ========================================================================== */
87
88
3fe53a83 89/* FIXME: not currently used, but should normalize with other terms. */
edfda783
AR
90void
91x_activate_menubar (struct frame *f)
92{
93 fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
94}
95
96
97/* Supposed to discard menubar and free storage. Since we share the
98 menubar among frames and update its context for the focused window,
99 there is nothing to do here. */
100void
101free_frame_menubar (struct frame *f)
102{
103 return;
104}
105
106
07b87a10
AR
107int
108popup_activated ()
109{
110 return popup_activated_flag;
111}
112
113
edfda783
AR
114/* --------------------------------------------------------------------------
115 Update menubar. Three cases:
116 1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
117 just top-level menu strings (OS X), or goto case (2) (GNUstep).
118 2) deep_p = 1, submenu = nil: Recompute all submenus.
119 3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
120 -------------------------------------------------------------------------- */
edfda783
AR
121void
122ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
123{
124 NSAutoreleasePool *pool;
125 id menu = [NSApp mainMenu];
126 static EmacsMenu *last_submenu = nil;
127 BOOL needsSet = NO;
128 const char *submenuTitle = [[submenu title] UTF8String];
129 extern int waiting_for_input;
130 int owfi;
131 Lisp_Object items;
132 widget_value *wv, *first_wv, *prev_wv = 0;
133 int i;
134
cbe0b5bf 135#if NSMENUPROFILE
edfda783
AR
136 struct timeb tb;
137 long t;
138#endif
139
140 NSTRACE (set_frame_menubar);
141
142 if (f != SELECTED_FRAME ())
143 return;
144 XSETFRAME (Vmenu_updating_frame, f);
145/*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
146
147 BLOCK_INPUT;
148 pool = [[NSAutoreleasePool alloc] init];
149
150 /* Menu may have been created automatically; if so, discard it. */
151 if ([menu isKindOfClass: [EmacsMenu class]] == NO)
152 {
153 [menu release];
154 menu = nil;
155 }
156
157 if (menu == nil)
158 {
159 menu = [[EmacsMenu alloc] initWithTitle: @"Emacs"];
160 needsSet = YES;
161 }
162 else
163 { /* close up anything on there */
164 id attMenu = [menu attachedMenu];
165 if (attMenu != nil)
166 [attMenu close];
167 }
168
cbe0b5bf 169#if NSMENUPROFILE
edfda783
AR
170 ftime (&tb);
171 t = -(1000*tb.time+tb.millitm);
172#endif
173
174 /* widget_value is a straightforward object translation of emacs's
175 Byzantine lisp menu structures */
176 wv = xmalloc_widget_value ();
177 wv->name = "Emacs";
178 wv->value = 0;
179 wv->enabled = 1;
180 wv->button_type = BUTTON_TYPE_NONE;
181 wv->help = Qnil;
182 first_wv = wv;
183
184#ifdef NS_IMPL_GNUSTEP
185 deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
186#endif
187
188 if (deep_p)
189 {
190 /* Fully parse one or more of the submenus. */
191 int n = 0;
192 int *submenu_start, *submenu_end;
193 int *submenu_top_level_items, *submenu_n_panes;
194 struct buffer *prev = current_buffer;
195 Lisp_Object buffer;
196 int specpdl_count = SPECPDL_INDEX ();
197 int previous_menu_items_used = f->menu_bar_items_used;
198 Lisp_Object *previous_items
199 = (Lisp_Object *) alloca (previous_menu_items_used
200 * sizeof (Lisp_Object));
201
202 /* lisp preliminaries */
203 buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
204 specbind (Qinhibit_quit, Qt);
205 specbind (Qdebug_on_next_call, Qnil);
206 record_unwind_save_match_data ();
207 if (NILP (Voverriding_local_map_menu_flag))
208 {
209 specbind (Qoverriding_terminal_local_map, Qnil);
210 specbind (Qoverriding_local_map, Qnil);
211 }
212 set_buffer_internal_1 (XBUFFER (buffer));
213
df2142db
AR
214 /* TODO: for some reason this is not needed in other terms,
215 but some menu updates call Info-extract-pointer which causes
216 abort-on-error if waiting-for-input. Needs further investigation. */
edfda783
AR
217 owfi = waiting_for_input;
218 waiting_for_input = 0;
219
220 /* lucid hook and possible reset */
221 safe_run_hooks (Qactivate_menubar_hook);
222 if (! NILP (Vlucid_menu_bar_dirty_flag))
223 call0 (Qrecompute_lucid_menubar);
224 safe_run_hooks (Qmenu_bar_update_hook);
225 FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f));
226
227 /* Now ready to go */
228 items = FRAME_MENU_BAR_ITEMS (f);
229
230 /* Save the frame's previous menu bar contents data */
231 if (previous_menu_items_used)
232 bcopy (XVECTOR (f->menu_bar_vector)->contents, previous_items,
233 previous_menu_items_used * sizeof (Lisp_Object));
234
235 /* parse stage 1: extract from lisp */
236 save_menu_items ();
237
238 menu_items = f->menu_bar_vector;
239 menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
240 submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
241 submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
242 submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
243 submenu_top_level_items
244 = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
245 init_menu_items ();
246 for (i = 0; i < XVECTOR (items)->size; i += 4)
247 {
248 Lisp_Object key, string, maps;
249
250 key = XVECTOR (items)->contents[i];
251 string = XVECTOR (items)->contents[i + 1];
252 maps = XVECTOR (items)->contents[i + 2];
253 if (NILP (string))
254 break;
255
df2142db
AR
256 /* FIXME: we'd like to only parse the needed submenu, but this
257 was causing crashes in the _common parsing code.. need to make
258 sure proper initialization done.. */
259/* if (submenu && strcmp (submenuTitle, SDATA (string)))
edfda783
AR
260 continue; */
261
262 submenu_start[i] = menu_items_used;
263
264 menu_items_n_panes = 0;
265 submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
266 submenu_n_panes[i] = menu_items_n_panes;
267 submenu_end[i] = menu_items_used;
268 n++;
269 }
270
271 finish_menu_items ();
272 waiting_for_input = owfi;
273
274
275 if (submenu && n == 0)
276 {
277 /* should have found a menu for this one but didn't */
278 fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
279 submenuTitle);
280 discard_menu_items ();
281 unbind_to (specpdl_count, Qnil);
282 [pool release];
283 UNBLOCK_INPUT;
284 return;
285 }
286
287 /* parse stage 2: insert into lucid 'widget_value' structures
288 [comments in other terms say not to evaluate lisp code here] */
289 wv = xmalloc_widget_value ();
290 wv->name = "menubar";
291 wv->value = 0;
292 wv->enabled = 1;
293 wv->button_type = BUTTON_TYPE_NONE;
294 wv->help = Qnil;
295 first_wv = wv;
296
297 for (i = 0; i < 4*n; i += 4)
298 {
299 menu_items_n_panes = submenu_n_panes[i];
300 wv = digest_single_submenu (submenu_start[i], submenu_end[i],
301 submenu_top_level_items[i]);
302 if (prev_wv)
303 prev_wv->next = wv;
304 else
305 first_wv->contents = wv;
306 /* Don't set wv->name here; GC during the loop might relocate it. */
307 wv->enabled = 1;
308 wv->button_type = BUTTON_TYPE_NONE;
309 prev_wv = wv;
310 }
311
312 set_buffer_internal_1 (prev);
313
314 /* Compare the new menu items with previous, and leave off if no change */
df2142db
AR
315 /* FIXME: following other terms here, but seems like this should be
316 done before parse stage 2 above, since its results aren't used */
edfda783
AR
317 if (previous_menu_items_used
318 && (!submenu || (submenu && submenu == last_submenu))
319 && menu_items_used == previous_menu_items_used)
320 {
321 for (i = 0; i < previous_menu_items_used; i++)
df2142db
AR
322 /* FIXME: this ALWAYS fails on Buffers menu items.. something
323 about their strings causes them to change every time, so we
324 double-check failures */
edfda783
AR
325 if (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i]))
326 if (!(STRINGP (previous_items[i])
327 && STRINGP (XVECTOR (menu_items)->contents[i])
328 && !strcmp (SDATA (previous_items[i]),
329 SDATA (XVECTOR (menu_items)->contents[i]))))
330 break;
331 if (i == previous_menu_items_used)
332 {
333 /* No change.. */
334
cbe0b5bf 335#if NSMENUPROFILE
edfda783
AR
336 ftime (&tb);
337 t += 1000*tb.time+tb.millitm;
338 fprintf (stderr, "NO CHANGE! CUTTING OUT after %ld msec.\n", t);
339#endif
340
341 free_menubar_widget_value_tree (first_wv);
342 discard_menu_items ();
343 unbind_to (specpdl_count, Qnil);
344 [pool release];
345 UNBLOCK_INPUT;
346 return;
347 }
348 }
349 /* The menu items are different, so store them in the frame */
df2142db 350 /* FIXME: this is not correct for single-submenu case */
edfda783
AR
351 f->menu_bar_vector = menu_items;
352 f->menu_bar_items_used = menu_items_used;
353
354 /* Calls restore_menu_items, etc., as they were outside */
355 unbind_to (specpdl_count, Qnil);
356
357 /* Parse stage 2a: now GC cannot happen during the lifetime of the
358 widget_value, so it's safe to store data from a Lisp_String */
359 wv = first_wv->contents;
360 for (i = 0; i < XVECTOR (items)->size; i += 4)
361 {
362 Lisp_Object string;
363 string = XVECTOR (items)->contents[i + 1];
364 if (NILP (string))
365 break;
366/* if (submenu && strcmp (submenuTitle, SDATA (string)))
367 continue; */
368
369 wv->name = (char *) SDATA (string);
370 update_submenu_strings (wv->contents);
371 wv = wv->next;
372 }
373
374 /* Now, update the NS menu; if we have a submenu, use that, otherwise
375 create a new menu for each sub and fill it. */
376 if (submenu)
377 {
378 for (wv = first_wv->contents; wv; wv = wv->next)
379 {
380 if (!strcmp (submenuTitle, wv->name))
381 {
382 [submenu fillWithWidgetValue: wv->contents];
383 last_submenu = submenu;
384 break;
385 }
386 }
387 }
388 else
389 {
390 [menu fillWithWidgetValue: first_wv->contents];
391 }
392
393 }
394 else
395 {
396 static int n_previous_strings = 0;
397 static char previous_strings[100][10];
398 static struct frame *last_f = NULL;
399 int n;
400 Lisp_Object string;
401
402 /* Make widget-value tree w/ just the top level menu bar strings */
403 items = FRAME_MENU_BAR_ITEMS (f);
404 if (NILP (items))
405 {
406 [pool release];
407 UNBLOCK_INPUT;
408 return;
409 }
410
411
412 /* check if no change.. this mechanism is a bit rough, but ready */
413 n = XVECTOR (items)->size / 4;
414 if (f == last_f && n_previous_strings == n)
415 {
416 for (i = 0; i<n; i++)
417 {
facfbbbd 418 string = AREF (items, 4*i+1);
edfda783 419
facfbbbd 420 if (EQ (string, make_number (0))) // FIXME: Why??? --Stef
edfda783
AR
421 continue;
422 if (NILP (string))
423 if (previous_strings[i][0])
424 break;
425 else
426 continue;
427 if (strncmp (previous_strings[i], SDATA (string), 10))
428 break;
429 }
430
431 if (i == n)
432 {
433 [pool release];
434 UNBLOCK_INPUT;
435 return;
436 }
437 }
438
439 [menu clear];
440 for (i = 0; i < XVECTOR (items)->size; i += 4)
441 {
442 string = XVECTOR (items)->contents[i + 1];
443 if (NILP (string))
444 break;
445
446 if (n < 100)
447 strncpy (previous_strings[i/4], SDATA (string), 10);
448
449 wv = xmalloc_widget_value ();
450 wv->name = (char *) SDATA (string);
451 wv->value = 0;
452 wv->enabled = 1;
453 wv->button_type = BUTTON_TYPE_NONE;
454 wv->help = Qnil;
455 wv->call_data = (void *) (EMACS_INT) (-1);
456
457#ifdef NS_IMPL_COCOA
458 /* we'll update the real copy under app menu when time comes */
459 if (!strcmp ("Services", wv->name))
460 {
461 /* but we need to make sure it will update on demand */
462 [svcsMenu setFrame: f];
463 [svcsMenu setDelegate: svcsMenu];
464 }
465 else
466#endif
467 [menu addSubmenuWithTitle: wv->name forFrame: f];
468
469 if (prev_wv)
470 prev_wv->next = wv;
471 else
472 first_wv->contents = wv;
473 prev_wv = wv;
474 }
475
476 last_f = f;
477 if (n < 100)
478 n_previous_strings = n;
479 else
480 n_previous_strings = 0;
481
482 }
483 free_menubar_widget_value_tree (first_wv);
484
485
cbe0b5bf 486#if NSMENUPROFILE
edfda783
AR
487 ftime (&tb);
488 t += 1000*tb.time+tb.millitm;
489 fprintf (stderr, "Menu update took %ld msec.\n", t);
490#endif
491
492 /* set main menu */
493 if (needsSet)
494 [NSApp setMainMenu: menu];
495
496 [pool release];
497 UNBLOCK_INPUT;
498
499}
500
501
502/* Main emacs core entry point for menubar menus: called to indicate that the
503 frame's menus have changed, and the *step representation should be updated
504 from Lisp. */
505void
506set_frame_menubar (struct frame *f, int first_time, int deep_p)
507{
508 ns_update_menubar (f, deep_p, nil);
509}
510
511
512/* Utility (from macmenu.c): is this item a separator? */
513static int
514name_is_separator (name)
515 const char *name;
516{
517 const char *start = name;
518
519 /* Check if name string consists of only dashes ('-'). */
520 while (*name == '-') name++;
521 /* Separators can also be of the form "--:TripleSuperMegaEtched"
522 or "--deep-shadow". We don't implement them yet, se we just treat
523 them like normal separators. */
524 return (*name == '\0' || start + 2 == name);
525}
526
527
528/* ==========================================================================
529
530 Menu: class implementation
531
532 ========================================================================== */
533
534
535/* Menu that can define itself from Emacs "widget_value"s and will lazily
536 update itself when user clicked. Based on Carbon/AppKit implementation
537 by Yamamoto Mitsuharu. */
538@implementation EmacsMenu
539
540/* override designated initializer */
541- initWithTitle: (NSString *)title
542{
543 if (self = [super initWithTitle: title])
544 [self setAutoenablesItems: NO];
545 return self;
546}
547
548
549/* used for top-level */
550- initWithTitle: (NSString *)title frame: (struct frame *)f
551{
552 [self initWithTitle: title];
553 frame = f;
554#ifdef NS_IMPL_COCOA
555 [self setDelegate: self];
556#endif
557 return self;
558}
559
560
561- (void)setFrame: (struct frame *)f
562{
563 frame = f;
564}
565
566
567/* delegate method called when a submenu is being opened: run a 'deep' call
568 to set_frame_menubar */
569- (void)menuNeedsUpdate: (NSMenu *)menu
570{
571 NSEvent *event = [[FRAME_NS_VIEW (frame) window] currentEvent];
572 /* HACK: Cocoa/Carbon will request update on every keystroke
573 via IsMenuKeyEvent -> CheckMenusForKeyEvent. These are not needed
574 since key equivalents are handled through emacs.
575 On Leopard, even keystroke events generate SystemDefined events, but
576 their subtype is 8. */
c96169a0
AR
577 if ([event type] != NSSystemDefined || [event subtype] == 8
578 /* Also, don't try this if from an event picked up asynchronously,
579 as lots of lisp evaluation happens in ns_update_menubar. */
580 || handling_signal != 0)
edfda783
AR
581 return;
582/*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
583 ns_update_menubar (frame, 1, self);
584}
585
586
587- (BOOL)performKeyEquivalent: (NSEvent *)theEvent
588{
589 if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
590 && FRAME_NS_VIEW (SELECTED_FRAME ()))
591 [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
592 return YES;
593}
594
595
c7cef62d
AR
596/* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
597 into an accelerator string. We are only able to display a single character
598 for an accelerator, together with an optional modifier combination. (Under
599 Carbon more control was possible, but in Cocoa multi-char strings passed to
600 NSMenuItem get ignored. For now we try to display a super-single letter
601 combo, and return the others as strings to be appended to the item title.
602 (This is signaled by setting keyEquivModMask to 0 for now.) */
edfda783
AR
603-(NSString *)parseKeyEquiv: (char *)key
604{
605 char *tpos = key;
c7cef62d
AR
606 keyEquivModMask = NSCommandKeyMask;
607
edfda783
AR
608 if (!key || !strlen (key))
609 return @"";
610
611 while (*tpos == ' ' || *tpos == '(')
612 tpos++;
c7cef62d
AR
613 if (*tpos != 's') {
614 keyEquivModMask = 0; /* signal */
615 return [NSString stringWithUTF8String: tpos];
616 }
edfda783
AR
617 return [NSString stringWithFormat: @"%c", tpos[2]];
618}
619
07b87a10 620
15034960 621- (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
edfda783 622{
15034960 623 NSMenuItem *item;
edfda783
AR
624 widget_value *wv = (widget_value *)wvptr;
625
626 if (name_is_separator (wv->name))
627 {
628 item = [NSMenuItem separatorItem];
629 [self addItem: item];
630 }
631 else
632 {
633 NSString *title, *keyEq;
634 title = [NSString stringWithUTF8String: wv->name];
635 if (title == nil)
636 title = @"< ? >"; /* (get out in the open so we know about it) */
637
638 keyEq = [self parseKeyEquiv: wv->key];
84ee8aba
AR
639#ifdef NS_IMPL_COCOA
640 /* OS X just ignores modifier strings longer than one character */
c7cef62d
AR
641 if (keyEquivModMask == 0)
642 title = [title stringByAppendingFormat: @" (%@)", keyEq];
84ee8aba 643#endif
edfda783
AR
644
645 item = [self addItemWithTitle: (NSString *)title
646 action: @selector (menuDown:)
647 keyEquivalent: keyEq];
c7cef62d 648 [item setKeyEquivalentModifierMask: keyEquivModMask];
edfda783
AR
649
650 [item setEnabled: wv->enabled];
651
652 /* Draw radio buttons and tickboxes */
653 if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
654 wv->button_type == BUTTON_TYPE_RADIO))
655 [item setState: NSOnState];
656 else
657 [item setState: NSOffState];
658
659 [item setTag: (int)wv->call_data];
660 }
661
662 return item;
663}
664
665
666/* convenience */
667-(void) clear
668{
669 int n;
670
671 for (n = [self numberOfItems]-1; n >= 0; n--)
672 {
673 NSMenuItem *item = [self itemAtIndex: n];
674 NSString *title = [item title];
675 if (([title length] == 0 || [@"Apple" isEqualToString: title])
676 && ![item isSeparatorItem])
677 continue;
678 [self removeItemAtIndex: n];
679 }
680}
681
682
683- (void)fillWithWidgetValue: (void *)wvptr
684{
685 widget_value *wv = (widget_value *)wvptr;
686
687 /* clear existing contents */
688 [self setMenuChangedMessagesEnabled: NO];
689 [self clear];
690
691 /* add new contents */
692 for (; wv != NULL; wv = wv->next)
693 {
15034960 694 NSMenuItem *item = [self addItemWithWidgetValue: wv];
edfda783
AR
695
696 if (wv->contents)
697 {
c96169a0 698 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
edfda783
AR
699
700 [self setSubmenu: submenu forItem: item];
701 [submenu fillWithWidgetValue: wv->contents];
702 [submenu release];
703 [item setAction: nil];
704 }
705 }
706
707 [self setMenuChangedMessagesEnabled: YES];
708#ifdef NS_IMPL_GNUSTEP
709 if ([[self window] isVisible])
710 [self sizeToFit];
711#else
712 if ([self supermenu] == nil)
713 [self sizeToFit];
714#endif
715}
716
717
718/* adds an empty submenu and returns it */
719- (EmacsMenu *)addSubmenuWithTitle: (char *)title forFrame: (struct frame *)f
720{
721 NSString *titleStr = [NSString stringWithUTF8String: title];
15034960
AR
722 NSMenuItem *item = [self addItemWithTitle: titleStr
723 action: nil /*@selector (menuDown:) */
724 keyEquivalent: @""];
edfda783
AR
725 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
726 [self setSubmenu: submenu forItem: item];
727 [submenu release];
728 return submenu;
729}
730
731/* run a menu in popup mode */
732- (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
733 keymaps: (int)keymaps
734{
735 EmacsView *view = FRAME_NS_VIEW (f);
736/* p = [view convertPoint:p fromView: nil]; */
737 p.y = NSHeight ([view frame]) - p.y;
738 NSEvent *e = [[view window] currentEvent];
739 NSEvent *event = [NSEvent mouseEventWithType: NSRightMouseDown
740 location: p
741 modifierFlags: 0
742 timestamp: [e timestamp]
743 windowNumber: [[view window] windowNumber]
744 context: [e context]
745 eventNumber: 0/*[e eventNumber] */
746 clickCount: 1
747 pressure: 0];
748 long retVal;
749
750 context_menu_value = -1;
751 [NSMenu popUpContextMenu: self withEvent: event forView: view];
752 retVal = context_menu_value;
753 context_menu_value = 0;
facfbbbd
SM
754 return retVal > 0
755 ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
756 : Qnil;
edfda783
AR
757}
758
759@end /* EmacsMenu */
760
761
762
763/* ==========================================================================
764
765 Context Menu: implementing functions
766
767 ========================================================================== */
768
769static Lisp_Object
770cleanup_popup_menu (Lisp_Object arg)
771{
772 discard_menu_items ();
773 return Qnil;
774}
775
776
777static Lisp_Object
778ns_popup_menu (Lisp_Object position, Lisp_Object menu)
779{
780 EmacsMenu *pmenu;
781 struct frame *f = NULL;
782 NSPoint p;
783 Lisp_Object window, x, y, tem, keymap, title;
784 struct gcpro gcpro1;
785 int specpdl_count = SPECPDL_INDEX (), specpdl_count2;
786 char *error_name = NULL;
787 int keymaps = 0;
788 widget_value *wv, *first_wv = 0;
789
790 NSTRACE (ns_popup_menu);
791
792 if (!NILP (position))
793 {
794 check_ns ();
795
796 if (EQ (position, Qt)
797 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
798 || EQ (XCAR (position), Qtool_bar))))
799 {
800 /* Use the mouse's current position. */
801 struct frame *new_f = SELECTED_FRAME ();
802
803 if (FRAME_TERMINAL (new_f)->mouse_position_hook)
804 (*FRAME_TERMINAL (new_f)->mouse_position_hook)
805 (&new_f, 0, 0, 0, &x, &y, 0);
806 if (new_f != 0)
807 XSETFRAME (window, new_f);
808 else
809 {
810 window = selected_window;
811 x = make_number (0);
812 y = make_number (0);
813 }
814 }
815 else
816 {
817 CHECK_CONS (position);
818 tem = Fcar (position);
819 if (XTYPE (tem) == Lisp_Cons)
820 {
821 window = Fcar (Fcdr (position));
822 x = Fcar (tem);
823 y = Fcar (Fcdr (tem));
824 }
825 else
826 {
827 tem = Fcar (Fcdr (position));
828 window = Fcar (tem);
829 tem = Fcar (Fcdr (Fcdr (tem)));
830 x = Fcar (tem);
831 y = Fcdr (tem);
832 }
833 }
834
835 CHECK_NUMBER (x);
836 CHECK_NUMBER (y);
837
838 if (FRAMEP (window))
839 {
840 f = XFRAME (window);
841
842 p.x = 0;
843 p.y = 0;
844 }
845 else
846 {
847 struct window *win = XWINDOW (window);
848 CHECK_LIVE_WINDOW (window);
849 f = XFRAME (WINDOW_FRAME (win));
850 p.x = FRAME_COLUMN_WIDTH (f) * WINDOW_LEFT_EDGE_COL (win);
851 p.y = FRAME_LINE_HEIGHT (f) * WINDOW_TOP_EDGE_LINE (win);
852 }
853
854 p.x += XINT (x); p.y += XINT (y);
855
856 XSETFRAME (Vmenu_updating_frame, f);
857 }
858 else
859 { /* no position given */
df2142db 860 /* FIXME: if called during dump, we need to stop precomputation of
edfda783
AR
861 key equivalents (see below) because the keydefs in ns-win.el have
862 not been loaded yet. */
863 if (noninteractive)
864 return Qnil;
865 Vmenu_updating_frame = Qnil;
866 }
867
868 /* now parse the lisp menus */
869 record_unwind_protect (unuse_menu_items, Qnil);
870 title = Qnil;
871 GCPRO1 (title);
872
873 /* Decode the menu items from what was specified. */
874
875 keymap = get_keymap (menu, 0, 0);
876 if (CONSP (keymap))
877 {
878 /* We were given a keymap. Extract menu info from the keymap. */
879 Lisp_Object prompt;
880
881 /* Extract the detailed info to make one pane. */
882 keymap_panes (&menu, 1, NILP (position));
883
884 /* Search for a string appearing directly as an element of the keymap.
885 That string is the title of the menu. */
886 prompt = Fkeymap_prompt (keymap);
887 title = NILP (prompt) ? build_string ("Select") : prompt;
888
889 /* Make that be the pane title of the first pane. */
890 if (!NILP (prompt) && menu_items_n_panes >= 0)
891 XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt;
892
893 keymaps = 1;
894 }
895 else if (CONSP (menu) && KEYMAPP (XCAR (menu)))
896 {
897 /* We were given a list of keymaps. */
898 int nmaps = XFASTINT (Flength (menu));
899 Lisp_Object *maps
900 = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object));
901 int i;
902
903 title = Qnil;
904
905 /* The first keymap that has a prompt string
906 supplies the menu title. */
907 for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem))
908 {
909 Lisp_Object prompt;
910
911 maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0);
912
913 prompt = Fkeymap_prompt (keymap);
914 if (NILP (title) && !NILP (prompt))
915 title = prompt;
916 }
917
918 /* Extract the detailed info to make one pane. */
919 keymap_panes (maps, nmaps, NILP (position));
920
921 /* Make the title be the pane title of the first pane. */
922 if (!NILP (title) && menu_items_n_panes >= 0)
923 XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title;
924
925 keymaps = 1;
926 }
927 else
928 {
929 /* We were given an old-fashioned menu. */
930 title = Fcar (menu);
931 CHECK_STRING (title);
932
933 list_of_panes (Fcdr (menu));
934
935 keymaps = 0;
936 }
937
938 unbind_to (specpdl_count, Qnil);
939
940 /* If no position given, that was a signal to just precompute and cache
941 key equivalents, which was a side-effect of what we just did. */
942 if (NILP (position))
943 {
944 discard_menu_items ();
945 UNGCPRO;
946 return Qnil;
947 }
948
949 record_unwind_protect (cleanup_popup_menu, Qnil);
950 BLOCK_INPUT;
951
952 /* now parse stage 2 as in ns_update_menubar */
953 wv = xmalloc_widget_value ();
954 wv->name = "contextmenu";
955 wv->value = 0;
956 wv->enabled = 1;
957 wv->button_type = BUTTON_TYPE_NONE;
958 wv->help = Qnil;
959 first_wv = wv;
960
961 specpdl_count2 = SPECPDL_INDEX ();
962
963#if 0
df2142db 964 /* FIXME: a couple of one-line differences prevent reuse */
edfda783
AR
965 wv = digest_single_submenu (0, menu_items_used, Qnil);
966#else
967 {
968 widget_value *save_wv = 0, *prev_wv = 0;
969 widget_value **submenu_stack
970 = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
971/* Lisp_Object *subprefix_stack
972 = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
973 int submenu_depth = 0;
974 int first_pane = 1;
975 int i;
976
977 /* Loop over all panes and items, filling in the tree. */
978 i = 0;
979 while (i < menu_items_used)
980 {
981 if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
982 {
983 submenu_stack[submenu_depth++] = save_wv;
984 save_wv = prev_wv;
985 prev_wv = 0;
986 first_pane = 1;
987 i++;
988 }
989 else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
990 {
991 prev_wv = save_wv;
992 save_wv = submenu_stack[--submenu_depth];
993 first_pane = 0;
994 i++;
995 }
996 else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
997 && submenu_depth != 0)
998 i += MENU_ITEMS_PANE_LENGTH;
999 /* Ignore a nil in the item list.
1000 It's meaningful only for dialog boxes. */
1001 else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
1002 i += 1;
1003 else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
1004 {
1005 /* Create a new pane. */
1006 Lisp_Object pane_name, prefix;
1007 char *pane_string;
1008
1009 pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
1010 prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
1011
1012#ifndef HAVE_MULTILINGUAL_MENU
1013 if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
1014 {
1015 pane_name = ENCODE_MENU_STRING (pane_name);
1016 ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
1017 }
1018#endif
1019 pane_string = (NILP (pane_name)
1020 ? "" : (char *) SDATA (pane_name));
1021 /* If there is just one top-level pane, put all its items directly
1022 under the top-level menu. */
1023 if (menu_items_n_panes == 1)
1024 pane_string = "";
1025
1026 /* If the pane has a meaningful name,
1027 make the pane a top-level menu item
1028 with its items as a submenu beneath it. */
1029 if (!keymaps && strcmp (pane_string, ""))
1030 {
1031 wv = xmalloc_widget_value ();
1032 if (save_wv)
1033 save_wv->next = wv;
1034 else
1035 first_wv->contents = wv;
1036 wv->name = pane_string;
1037 if (keymaps && !NILP (prefix))
1038 wv->name++;
1039 wv->value = 0;
1040 wv->enabled = 1;
1041 wv->button_type = BUTTON_TYPE_NONE;
1042 wv->help = Qnil;
1043 save_wv = wv;
1044 prev_wv = 0;
1045 }
1046 else if (first_pane)
1047 {
1048 save_wv = wv;
1049 prev_wv = 0;
1050 }
1051 first_pane = 0;
1052 i += MENU_ITEMS_PANE_LENGTH;
1053 }
1054 else
1055 {
1056 /* Create a new item within current pane. */
1057 Lisp_Object item_name, enable, descrip, def, type, selected, help;
1058 item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
1059 enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
1060 descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
1061 def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
1062 type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
1063 selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
1064 help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
1065
1066#ifndef HAVE_MULTILINGUAL_MENU
1067 if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
1068 {
1069 item_name = ENCODE_MENU_STRING (item_name);
1070 ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
1071 }
1072
1073 if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
1074 {
1075 descrip = ENCODE_MENU_STRING (descrip);
1076 ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
1077 }
1078#endif /* not HAVE_MULTILINGUAL_MENU */
1079
1080 wv = xmalloc_widget_value ();
1081 if (prev_wv)
1082 prev_wv->next = wv;
1083 else
1084 save_wv->contents = wv;
1085 wv->name = (char *) SDATA (item_name);
1086 if (!NILP (descrip))
1087 wv->key = (char *) SDATA (descrip);
1088 wv->value = 0;
1089 /* If this item has a null value,
1090 make the call_data null so that it won't display a box
1091 when the mouse is on it. */
facfbbbd
SM
1092 wv->call_data
1093 = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
edfda783
AR
1094 wv->enabled = !NILP (enable);
1095
1096 if (NILP (type))
1097 wv->button_type = BUTTON_TYPE_NONE;
1098 else if (EQ (type, QCtoggle))
1099 wv->button_type = BUTTON_TYPE_TOGGLE;
1100 else if (EQ (type, QCradio))
1101 wv->button_type = BUTTON_TYPE_RADIO;
1102 else
1103 abort ();
1104
1105 wv->selected = !NILP (selected);
1106
1107 if (! STRINGP (help))
1108 help = Qnil;
1109
1110 wv->help = help;
1111
1112 prev_wv = wv;
1113
1114 i += MENU_ITEMS_ITEM_LENGTH;
1115 }
1116 }
1117 }
1118#endif
1119
1120 if (!NILP (title))
1121 {
1122 widget_value *wv_title = xmalloc_widget_value ();
1123 widget_value *wv_sep = xmalloc_widget_value ();
1124
1125 /* Maybe replace this separator with a bitmap or owner-draw item
1126 so that it looks better. Having two separators looks odd. */
1127 wv_sep->name = "--";
1128 wv_sep->next = first_wv->contents;
1129 wv_sep->help = Qnil;
1130
1131#ifndef HAVE_MULTILINGUAL_MENU
1132 if (STRING_MULTIBYTE (title))
1133 title = ENCODE_MENU_STRING (title);
1134#endif
1135
1136 wv_title->name = (char *) SDATA (title);
15034960 1137 wv_title->enabled = NO;
edfda783
AR
1138 wv_title->button_type = BUTTON_TYPE_NONE;
1139 wv_title->help = Qnil;
1140 wv_title->next = wv_sep;
1141 first_wv->contents = wv_title;
1142 }
1143
1144 pmenu = [[EmacsMenu alloc] initWithTitle:
1145 [NSString stringWithUTF8String: SDATA (title)]];
1146 [pmenu fillWithWidgetValue: first_wv->contents];
1147 free_menubar_widget_value_tree (first_wv);
1148 unbind_to (specpdl_count2, Qnil);
1149
07b87a10 1150 popup_activated_flag = 1;
edfda783 1151 tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
07b87a10 1152 popup_activated_flag = 0;
edfda783
AR
1153 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1154
1155 UNBLOCK_INPUT;
07b87a10 1156 discard_menu_items ();
edfda783
AR
1157 unbind_to (specpdl_count, Qnil);
1158 UNGCPRO;
1159
1160 if (error_name) error (error_name);
1161 return tem;
1162}
1163
1164
1165
1166
1167/* ==========================================================================
1168
1169 Toolbar: externally-called functions
1170
1171 ========================================================================== */
1172
1173void
1174free_frame_tool_bar (FRAME_PTR f)
1175/* --------------------------------------------------------------------------
1176 Under NS we just hide the toolbar until it might be needed again.
1177 -------------------------------------------------------------------------- */
1178{
1179 [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1180}
1181
1182void
1183update_frame_tool_bar (FRAME_PTR f)
1184/* --------------------------------------------------------------------------
1185 Update toolbar contents
1186 -------------------------------------------------------------------------- */
1187{
1188 int i;
1189 EmacsToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar];
1190
edfda783
AR
1191 [toolbar clearActive];
1192
1193 /* update EmacsToolbar as in GtkUtils, build items list */
1194 for (i = 0; i < f->n_tool_bar_items; ++i)
1195 {
1196#define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1197 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1198
1199 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1200 BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1201 int idx;
1202 int img_id;
1203 struct image *img;
1204 Lisp_Object image;
1205 Lisp_Object helpObj;
1206 char *helpText;
1207
1208 /* If image is a vector, choose the image according to the
1209 button state. */
1210 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1211 if (VECTORP (image))
1212 {
1213 /* NS toolbar auto-computes disabled and selected images */
1214 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1215 xassert (ASIZE (image) >= idx);
1216 image = AREF (image, idx);
1217 }
1218 else
1219 {
1220 idx = -1;
1221 }
1222 /* Ignore invalid image specifications. */
1223 if (!valid_image_p (image))
1224 {
1225 NSLog (@"Invalid image for toolbar item");
1226 continue;
1227 }
1228
1229 img_id = lookup_image (f, image);
1230 img = IMAGE_FROM_ID (f, img_id);
1231 prepare_image_for_display (f, img);
1232
1233 if (img->load_failed_p || img->pixmap == nil)
1234 {
1235 NSLog (@"Could not prepare toolbar image for display.");
1236 continue;
1237 }
1238
1239 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1240 if (NILP (helpObj))
1241 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1242 helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1243
1244 [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1245 enabled: enabled_p];
1246#undef TOOLPROP
1247 }
1248
1249 if (![toolbar isVisible])
1250 [toolbar setVisible: YES];
1251
1252 if ([toolbar changed])
1253 {
1254 /* inform app that toolbar has changed */
1255 NSDictionary *dict = [toolbar configurationDictionary];
1256 NSMutableDictionary *newDict = [dict mutableCopy];
1257 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1258 NSObject *key;
1259 while ((key = [keys nextObject]) != nil)
1260 {
1261 NSObject *val = [dict objectForKey: key];
1262 if ([val isKindOfClass: [NSArray class]])
1263 {
1264 [newDict setObject:
1265 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1266 forKey: key];
1267 break;
1268 }
1269 }
1270 [toolbar setConfigurationFromDictionary: newDict];
1271 [newDict release];
1272 }
1273
1274}
1275
1276
1277/* ==========================================================================
1278
1279 Toolbar: class implementation
1280
1281 ========================================================================== */
1282
1283@implementation EmacsToolbar
1284
1285- initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1286{
1287 self = [super initWithIdentifier: identifier];
1288 emacsView = view;
1289 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1290 [self setSizeMode: NSToolbarSizeModeSmall];
1291 [self setDelegate: self];
1292 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1293 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1294 prevEnablement = enablement = 0L;
1295 return self;
1296}
1297
1298- (void)dealloc
1299{
1300 [prevIdentifiers release];
1301 [activeIdentifiers release];
1302 [identifierToItem release];
1303 [super dealloc];
1304}
1305
1306- (void) clearActive
1307{
1308 [prevIdentifiers release];
1309 prevIdentifiers = [activeIdentifiers copy];
1310 [activeIdentifiers removeAllObjects];
1311 prevEnablement = enablement;
1312 enablement = 0L;
1313}
1314
1315- (BOOL) changed
1316{
1317 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1318 enablement == prevEnablement ? NO : YES;
1319}
1320
1321- (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1322 helpText: (char *)help enabled: (BOOL)enabled
1323{
1324 /* 1) come up w/identifier */
facfbbbd
SM
1325 NSString *identifier
1326 = [NSString stringWithFormat: @"%u", [img hash]];
edfda783
AR
1327
1328 /* 2) create / reuse item */
1329 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1330 if (item == nil)
1331 {
1332 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1333 autorelease];
1334 [item setImage: img];
1335 [item setToolTip: [NSString stringWithCString: help]];
1336 [item setTarget: emacsView];
1337 [item setAction: @selector (toolbarClicked:)];
1338 }
1339
1340 [item setTag: idx];
1341 [item setEnabled: enabled];
1342
1343 /* 3) update state */
1344 [identifierToItem setObject: item forKey: identifier];
1345 [activeIdentifiers addObject: identifier];
1346 enablement = (enablement << 1) | (enabled == YES);
1347}
1348
1349/* This overrides super's implementation, which automatically sets
1350 all items to enabled state (for some reason). */
1351- (void)validateVisibleItems { }
1352
1353
1354/* delegate methods */
1355
1356- (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1357 itemForItemIdentifier: (NSString *)itemIdentifier
1358 willBeInsertedIntoToolbar: (BOOL)flag
1359{
1360 /* look up NSToolbarItem by identifier and return... */
1361 return [identifierToItem objectForKey: itemIdentifier];
1362}
1363
1364- (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1365{
1366 /* return entire set.. */
1367 return activeIdentifiers;
1368}
1369
1370/* for configuration palette (not yet supported) */
1371- (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1372{
1373 /* return entire set... */
1374 return [identifierToItem allKeys];
1375}
1376
1377/* optional and unneeded */
1378/* - toolbarWillAddItem: (NSNotification *)notification { } */
1379/* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1380/* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1381
1382@end /* EmacsToolbar */
1383
1384
1385
1386/* ==========================================================================
1387
1388 Tooltip: class implementation
1389
1390 ========================================================================== */
1391
1392/* Needed because NeXTstep does not provide enough control over tooltip
1393 display. */
1394@implementation EmacsTooltip
1395
1396- init
1397{
1398 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1399 blue: 0.792 alpha: 0.95];
1400 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1401 NSFont *sfont = [font screenFont];
1402 int height = [sfont ascender] - [sfont descender];
1403/*[font boundingRectForFont].size.height; */
1404 NSRect r = NSMakeRect (0, 0, 100, height+6);
1405
1406 textField = [[NSTextField alloc] initWithFrame: r];
1407 [textField setFont: font];
1408 [textField setBackgroundColor: col];
1409
1410 [textField setEditable: NO];
1411 [textField setSelectable: NO];
1412 [textField setBordered: YES];
1413 [textField setBezeled: YES];
1414 [textField setDrawsBackground: YES];
1415
1416 win = [[NSWindow alloc]
1417 initWithContentRect: [textField frame]
1418 styleMask: 0
1419 backing: NSBackingStoreBuffered
1420 defer: YES];
1421 [win setReleasedWhenClosed: NO];
1422 [win setDelegate: self];
1423 [[win contentView] addSubview: textField];
1424/* [win setBackgroundColor: col]; */
1425 [win setOpaque: NO];
1426
1427 return self;
1428}
1429
1430- (void) dealloc
1431{
1432 [win close];
1433 [win release];
1434 [textField release];
1435 [super dealloc];
1436}
1437
1438- (void) setText: (char *)text
1439{
1440 NSString *str = [NSString stringWithUTF8String: text];
1441 NSRect r = [textField frame];
1442 r.size.width = [[[textField font] screenFont] widthOfString: str] + 8;
1443 [textField setFrame: r];
1444 [textField setStringValue: str];
1445}
1446
1447- (void) showAtX: (int)x Y: (int)y for: (int)seconds
1448{
1449 NSRect wr = [win frame];
1450
1451 wr.origin = NSMakePoint (x, y);
1452 wr.size = [textField frame].size;
1453
1454 [win setFrame: wr display: YES];
1455 [win orderFront: self];
1456 [win display];
1457 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1458 selector: @selector (hide)
1459 userInfo: nil repeats: NO];
1460 [timer retain];
1461}
1462
1463- (void) hide
1464{
1465 [win close];
1466 if (timer != nil)
1467 {
1468 if ([timer isValid])
1469 [timer invalidate];
1470 [timer release];
1471 timer = nil;
1472 }
1473}
1474
1475- (BOOL) isActive
1476{
1477 return timer != nil;
1478}
1479
1480- (NSRect) frame
1481{
1482 return [textField frame];
1483}
1484
1485@end /* EmacsTooltip */
1486
1487
1488
1489/* ==========================================================================
1490
1491 Popup Dialog: implementing functions
1492
1493 ========================================================================== */
1494
c96169a0
AR
1495
1496static Lisp_Object
1497pop_down_menu (Lisp_Object arg)
1498{
1499 struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
ba301db3
AR
1500 if (popup_activated_flag)
1501 {
1502 popup_activated_flag = 0;
1503 BLOCK_INPUT;
1504 [NSApp endModalSession: popupSession];
1505 [((EmacsDialogPanel *) (p->pointer)) close];
1506 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1507 UNBLOCK_INPUT;
1508 }
c96169a0
AR
1509 return Qnil;
1510}
1511
1512
edfda783
AR
1513Lisp_Object
1514ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1515{
1516 id dialog;
1517 Lisp_Object window, tem;
1518 struct frame *f;
1519 NSPoint p;
1520 BOOL isQ;
1521
1522 NSTRACE (x-popup-dialog);
1523
1524 check_ns ();
1525
1526 isQ = NILP (header);
1527
8612b71a
AR
1528 if (EQ (position, Qt)
1529 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1530 || EQ (XCAR (position), Qtool_bar))))
edfda783
AR
1531 {
1532 window = selected_window;
1533 }
1534 else if (CONSP (position))
1535 {
1536 Lisp_Object tem;
1537 tem = Fcar (position);
1538 if (XTYPE (tem) == Lisp_Cons)
1539 window = Fcar (Fcdr (position));
1540 else
1541 {
1542 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1543 window = Fcar (tem); /* POSN_WINDOW (tem) */
1544 }
1545 }
8612b71a 1546 else if (WINDOWP (position) || FRAMEP (position))
edfda783
AR
1547 {
1548 window = position;
1549 }
1550 else
8612b71a
AR
1551 window = Qnil;
1552
edfda783
AR
1553 if (FRAMEP (window))
1554 f = XFRAME (window);
8612b71a 1555 else if (WINDOWP (window))
edfda783
AR
1556 {
1557 CHECK_LIVE_WINDOW (window);
1558 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1559 }
8612b71a
AR
1560 else
1561 CHECK_WINDOW (window);
1562
edfda783
AR
1563 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1564 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
3175b12a
AR
1565
1566 BLOCK_INPUT;
edfda783
AR
1567 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1568 isQuestion: isQ];
c96169a0
AR
1569 {
1570 int specpdl_count = SPECPDL_INDEX ();
1571 record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1572 popup_activated_flag = 1;
1573 tem = [dialog runDialogAt: p];
ba301db3 1574 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
c96169a0 1575 }
3175b12a 1576 UNBLOCK_INPUT;
c96169a0 1577
edfda783
AR
1578 return tem;
1579}
1580
1581
1582/* ==========================================================================
1583
1584 Popup Dialog: class implementation
1585
1586 ========================================================================== */
1587
1588@interface FlippedView : NSView
1589{
1590}
1591@end
1592
1593@implementation FlippedView
1594- (BOOL)isFlipped
1595{
1596 return YES;
1597}
1598@end
1599
1600@implementation EmacsDialogPanel
1601
1602#define SPACER 8.0
1603#define ICONSIZE 64.0
1604#define TEXTHEIGHT 20.0
1605#define MINCELLWIDTH 90.0
1606
1607- initWithContentRect: (NSRect)contentRect styleMask: (unsigned int)aStyle
1608 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1609{
1610 NSSize spacing = {SPACER, SPACER};
1611 NSRect area;
1612 char this_cmd_name[80];
facfbbbd 1613 id cell;
edfda783
AR
1614 static NSImageView *imgView;
1615 static FlippedView *contentView;
1616
1617 if (imgView == nil)
1618 {
1619 NSImage *img;
1620 area.origin.x = 3*SPACER;
1621 area.origin.y = 2*SPACER;
1622 area.size.width = ICONSIZE;
1623 area.size.height= ICONSIZE;
1624 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1625 [img setScalesWhenResized: YES];
1626 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1627 imgView = [[NSImageView alloc] initWithFrame: area];
1628 [imgView setImage: img];
1629 [imgView setEditable: NO];
1630 [img release];
1631 }
1632
1633 aStyle = NSTitledWindowMask;
1634 flag = YES;
1635 rows = 0;
1636 cols = 1;
1637 [super initWithContentRect: contentRect styleMask: aStyle
1638 backing: backingType defer: flag];
1639 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1640 [self setContentView: contentView];
1641
1642 [[self contentView] setAutoresizesSubviews: YES];
1643
1644 [[self contentView] addSubview: imgView];
1645 [self setTitle: @""];
1646
1647 area.origin.x += ICONSIZE+2*SPACER;
1648/* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1649 area.size.width = 400;
1650 area.size.height= TEXTHEIGHT;
1651 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1652 [[self contentView] addSubview: command];
1653 [command setStringValue: @"Emacs"];
1654 [command setDrawsBackground: NO];
1655 [command setBezeled: NO];
1656 [command setSelectable: NO];
1657 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1658
1659/* area.origin.x = ICONSIZE+2*SPACER;
1660 area.origin.y = TEXTHEIGHT + 2*SPACER;
1661 area.size.width = 400;
1662 area.size.height= 2;
1663 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1664 [[self contentView] addSubview: tem];
1665 [tem setTitlePosition: NSNoTitle];
1666 [tem setAutoresizingMask: NSViewWidthSizable];*/
1667
1668/* area.origin.x = ICONSIZE+2*SPACER; */
1669 area.origin.y += TEXTHEIGHT+SPACER;
1670 area.size.width = 400;
1671 area.size.height= TEXTHEIGHT;
1672 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1673 [[self contentView] addSubview: title];
1674 [title setDrawsBackground: NO];
1675 [title setBezeled: NO];
1676 [title setSelectable: NO];
1677 [title setFont: [NSFont systemFontOfSize: 11.0]];
1678
1679 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1680 [cell setBordered: NO];
1681 [cell setEnabled: NO];
1682 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1683 [cell setBezelStyle: NSRoundedBezelStyle];
1684
1685 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1686 mode: NSHighlightModeMatrix
1687 prototype: cell
1688 numberOfRows: 0
1689 numberOfColumns: 1];
1690 [[self contentView] addSubview: matrix];
1691 [matrix release];
1692 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1693 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1694 [matrix setIntercellSpacing: spacing];
1695
1696 [self setOneShot: YES];
1697 [self setReleasedWhenClosed: YES];
1698 [self setHidesOnDeactivate: YES];
1699 return self;
1700}
1701
1702
1703- (BOOL)windowShouldClose: (id)sender
1704{
facfbbbd 1705 [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
edfda783
AR
1706 return NO;
1707}
1708
1709
1710void process_dialog (id window, Lisp_Object list)
1711{
1712 Lisp_Object item;
1713 int row = 0;
1714
1715 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1716 {
1717 item = XCAR (list);
1718 if (XTYPE (item) == Lisp_String)
1719 {
1720 [window addString: XSTRING (item)->data row: row++];
1721 }
1722 else if (XTYPE (item) == Lisp_Cons)
1723 {
1724 [window addButton: XSTRING (XCAR (item))->data
1725 value: XCDR (item) row: row++];
1726 }
1727 else if (NILP (item))
1728 {
1729 [window addSplit];
1730 row = 0;
1731 }
1732 }
1733}
1734
1735
1736- addButton: (char *)str value: (Lisp_Object)val row: (int)row
1737{
1738 id cell;
1739
1740 if (row >= rows)
1741 {
1742 [matrix addRow];
1743 rows++;
1744 }
1745 cell = [matrix cellAtRow: row column: cols-1];
1746 [cell setTarget: self];
1747 [cell setAction: @selector (clicked: )];
1748 [cell setTitle: [NSString stringWithUTF8String: str]];
facfbbbd 1749 [cell setTag: XHASH (val)]; // FIXME: BIG UGLY HACK!!
edfda783
AR
1750 [cell setBordered: YES];
1751 [cell setEnabled: YES];
1752
1753 return self;
1754}
1755
1756
1757- addString: (char *)str row: (int)row
1758{
1759 id cell;
1760
1761 if (row >= rows)
1762 {
1763 [matrix addRow];
1764 rows++;
1765 }
1766 cell = [matrix cellAtRow: row column: cols-1];
1767 [cell setTitle: [NSString stringWithUTF8String: str]];
1768 [cell setBordered: YES];
1769 [cell setEnabled: NO];
1770
1771 return self;
1772}
1773
1774
1775- addSplit
1776{
1777 [matrix addColumn];
1778 cols++;
1779 return self;
1780}
1781
1782
1783- clicked: sender
1784{
1785 NSArray *sellist = nil;
facfbbbd 1786 EMACS_INT seltag;
edfda783
AR
1787
1788 sellist = [sender selectedCells];
1789 if ([sellist count]<1)
1790 return self;
1791
facfbbbd 1792 seltag = [[sellist objectAtIndex: 0] tag];
4e622592 1793 if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
edfda783
AR
1794 [NSApp stopModalWithCode: seltag];
1795 return self;
1796}
1797
1798
1799- initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1800{
1801 Lisp_Object head;
1802 [super init];
1803
1804 if (XTYPE (contents) == Lisp_Cons)
1805 {
1806 head = Fcar (contents);
1807 process_dialog (self, Fcdr (contents));
1808 }
1809 else
1810 head = contents;
1811
1812 if (XTYPE (head) == Lisp_String)
1813 [title setStringValue:
1814 [NSString stringWithUTF8String: XSTRING (head)->data]];
1815 else if (isQ == YES)
1816 [title setStringValue: @"Question"];
1817 else
1818 [title setStringValue: @"Information"];
1819
1820 {
1821 int i;
1822 NSRect r, s, t;
1823
1824 if (cols == 1 && rows > 1) /* Never told where to split */
1825 {
1826 [matrix addColumn];
1827 for (i = 0; i<rows/2; i++)
1828 {
1829 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1830 atRow: i column: 1];
1831 [matrix removeRow: (rows+1)/2];
1832 }
1833 }
1834
1835 [matrix sizeToFit];
1836 {
1837 NSSize csize = [matrix cellSize];
1838 if (csize.width < MINCELLWIDTH)
1839 {
1840 csize.width = MINCELLWIDTH;
1841 [matrix setCellSize: csize];
1842 [matrix sizeToCells];
1843 }
1844 }
1845
1846 [title sizeToFit];
1847 [command sizeToFit];
1848
1849 t = [matrix frame];
1850 r = [title frame];
1851 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1852 {
1853 t.origin.x = r.origin.x;
1854 t.size.width = r.size.width;
1855 }
1856 r = [command frame];
1857 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1858 {
1859 t.origin.x = r.origin.x;
1860 t.size.width = r.size.width;
1861 }
1862
1863 r = [self frame];
1864 s = [(NSView *)[self contentView] frame];
1865 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1866 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1867 [self setFrame: r display: NO];
1868 }
1869
1870 return self;
1871}
1872
1873
1874- (void)dealloc
1875{
1876 { [super dealloc]; return; };
1877}
1878
1879
1880- (Lisp_Object)runDialogAt: (NSPoint)p
1881{
edfda783
AR
1882 int ret;
1883
ba301db3 1884 /* initiate a session that will be ended by pop_down_menu */
3175b12a 1885 popupSession = [NSApp beginModalSessionForWindow: self];
c96169a0 1886 while (popup_activated_flag
3175b12a
AR
1887 && (ret = [NSApp runModalSession: popupSession])
1888 == NSRunContinuesResponse)
edfda783 1889 {
3175b12a
AR
1890 /* Run this for timers.el, indep of atimers; might not return.
1891 TODO: use return value to avoid calling every iteration. */
1892 timer_check (1);
1893 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
edfda783 1894 }
edfda783 1895
3175b12a 1896 { /* FIXME: BIG UGLY HACK!!! */
facfbbbd
SM
1897 Lisp_Object tmp;
1898 *(EMACS_INT*)(&tmp) = ret;
1899 return tmp;
1900 }
edfda783
AR
1901}
1902
1903@end
1904
1905
edfda783
AR
1906/* ==========================================================================
1907
1908 Lisp definitions
1909
1910 ========================================================================== */
1911
1912DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
eb6f7ed0 1913 doc: /* Cause the NS menu to be re-calculated. */)
edfda783
AR
1914 ()
1915{
1916 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1917 return Qnil;
1918}
1919
1920
1921DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
eb6f7ed0
CY
1922 doc: /* Pop up a deck-of-cards menu and return user's selection.
1923POSITION is a position specification. This is either a mouse button event
1924or a list ((XOFFSET YOFFSET) WINDOW)
1925where XOFFSET and YOFFSET are positions in pixels from the top left
1926corner of WINDOW. (WINDOW may be a window or a frame object.)
1927This controls the position of the top left of the menu as a whole.
1928If POSITION is t, it means to use the current mouse position.
1929
1930MENU is a specifier for a menu. For the simplest case, MENU is a keymap.
1931The menu items come from key bindings that have a menu string as well as
1932a definition; actually, the \"definition\" in such a key binding looks like
1933\(STRING . REAL-DEFINITION). To give the menu a title, put a string into
1934the keymap as a top-level element.
1935
1936If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.
1937Otherwise, REAL-DEFINITION should be a valid key binding definition.
1938
1939You can also use a list of keymaps as MENU.
1940 Then each keymap makes a separate pane.
1941
1942When MENU is a keymap or a list of keymaps, the return value is the
1943list of events corresponding to the user's choice. Note that
1944`x-popup-menu' does not actually execute the command bound to that
1945sequence of events.
1946
1947Alternatively, you can specify a menu of multiple panes
1948 with a list of the form (TITLE PANE1 PANE2...),
1949where each pane is a list of form (TITLE ITEM1 ITEM2...).
1950Each ITEM is normally a cons cell (STRING . VALUE);
1951but a string can appear as an item--that makes a nonselectable line
1952in the menu.
1953With this form of menu, the return value is VALUE from the chosen item. */)
edfda783
AR
1954 (position, menu)
1955 Lisp_Object position, menu;
1956{
1957 return ns_popup_menu (position, menu);
1958}
1959
1960
1961DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1962 doc: /* Pop up a dialog box and return user's selection.
1963POSITION specifies which frame to use.
1964This is normally a mouse button event or a window or frame.
1965If POSITION is t, it means to use the frame the mouse is on.
1966The dialog box appears in the middle of the specified frame.
1967
1968CONTENTS specifies the alternatives to display in the dialog box.
1969It is a list of the form (DIALOG ITEM1 ITEM2...).
1970Each ITEM is a cons cell (STRING . VALUE).
1971The return value is VALUE from the chosen item.
1972
1973An ITEM may also be just a string--that makes a nonselectable item.
1974An ITEM may also be nil--that means to put all preceding items
1975on the left of the dialog box and all following items on the right.
1976\(By default, approximately half appear on each side.)
1977
1978If HEADER is non-nil, the frame title for the box is "Information",
1979otherwise it is "Question".
1980
1981If the user gets rid of the dialog box without making a valid choice,
1982for instance using the window manager, then this produces a quit and
1983`x-popup-dialog' does not return. */)
1984 (position, contents, header)
1985 Lisp_Object position, contents, header;
1986{
1987 return ns_popup_dialog (position, contents, header);
1988}
1989
07b87a10
AR
1990DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1991 doc: /* Return t if a menu or popup dialog is active. */)
1992 ()
1993{
1994 return popup_activated () ? Qt : Qnil;
1995}
edfda783
AR
1996
1997/* ==========================================================================
1998
1999 Lisp interface declaration
2000
2001 ========================================================================== */
2002
2003void
2004syms_of_nsmenu ()
2005{
2006 defsubr (&Sx_popup_menu);
2007 defsubr (&Sx_popup_dialog);
2008 defsubr (&Sns_reset_menu);
07b87a10 2009 defsubr (&Smenu_or_popup_active_p);
edfda783
AR
2010 staticpro (&menu_items);
2011 menu_items = Qnil;
2012
2013 Qdebug_on_next_call = intern ("debug-on-next-call");
2014 staticpro (&Qdebug_on_next_call);
2015}
0ae1e5e5 2016
2f8e74bb 2017// arch-tag: 75773656-52e5-4c44-a398-47bd87b32619