* syntax.c (Fchar_syntax): Minor doc fix (Bug#119).
[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];
5ee6f629
DR
1442 NSSize textSize = [str sizeWithAttributes:
1443 [NSDictionary dictionaryWithObject: [[textField font] screenFont]
1444 forKey: NSFontAttributeName]];
1445 NSSize padSize = [[[textField font] screenFont]
1446 boundingRectForFont].size;
1447
1448 r.size.width = textSize.width + padSize.width/2;
1449 r.size.height = textSize.height + padSize.height/2;
edfda783
AR
1450 [textField setFrame: r];
1451 [textField setStringValue: str];
1452}
1453
1454- (void) showAtX: (int)x Y: (int)y for: (int)seconds
1455{
1456 NSRect wr = [win frame];
1457
1458 wr.origin = NSMakePoint (x, y);
1459 wr.size = [textField frame].size;
1460
1461 [win setFrame: wr display: YES];
1462 [win orderFront: self];
1463 [win display];
1464 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1465 selector: @selector (hide)
1466 userInfo: nil repeats: NO];
1467 [timer retain];
1468}
1469
1470- (void) hide
1471{
1472 [win close];
1473 if (timer != nil)
1474 {
1475 if ([timer isValid])
1476 [timer invalidate];
1477 [timer release];
1478 timer = nil;
1479 }
1480}
1481
1482- (BOOL) isActive
1483{
1484 return timer != nil;
1485}
1486
1487- (NSRect) frame
1488{
1489 return [textField frame];
1490}
1491
1492@end /* EmacsTooltip */
1493
1494
1495
1496/* ==========================================================================
1497
1498 Popup Dialog: implementing functions
1499
1500 ========================================================================== */
1501
c96169a0
AR
1502
1503static Lisp_Object
1504pop_down_menu (Lisp_Object arg)
1505{
1506 struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
ba301db3
AR
1507 if (popup_activated_flag)
1508 {
1509 popup_activated_flag = 0;
1510 BLOCK_INPUT;
1511 [NSApp endModalSession: popupSession];
1512 [((EmacsDialogPanel *) (p->pointer)) close];
1513 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1514 UNBLOCK_INPUT;
1515 }
c96169a0
AR
1516 return Qnil;
1517}
1518
1519
edfda783
AR
1520Lisp_Object
1521ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1522{
1523 id dialog;
1524 Lisp_Object window, tem;
1525 struct frame *f;
1526 NSPoint p;
1527 BOOL isQ;
1528
1529 NSTRACE (x-popup-dialog);
1530
1531 check_ns ();
1532
1533 isQ = NILP (header);
1534
8612b71a
AR
1535 if (EQ (position, Qt)
1536 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1537 || EQ (XCAR (position), Qtool_bar))))
edfda783
AR
1538 {
1539 window = selected_window;
1540 }
1541 else if (CONSP (position))
1542 {
1543 Lisp_Object tem;
1544 tem = Fcar (position);
1545 if (XTYPE (tem) == Lisp_Cons)
1546 window = Fcar (Fcdr (position));
1547 else
1548 {
1549 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1550 window = Fcar (tem); /* POSN_WINDOW (tem) */
1551 }
1552 }
8612b71a 1553 else if (WINDOWP (position) || FRAMEP (position))
edfda783
AR
1554 {
1555 window = position;
1556 }
1557 else
8612b71a
AR
1558 window = Qnil;
1559
edfda783
AR
1560 if (FRAMEP (window))
1561 f = XFRAME (window);
8612b71a 1562 else if (WINDOWP (window))
edfda783
AR
1563 {
1564 CHECK_LIVE_WINDOW (window);
1565 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1566 }
8612b71a
AR
1567 else
1568 CHECK_WINDOW (window);
1569
edfda783
AR
1570 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1571 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
3175b12a
AR
1572
1573 BLOCK_INPUT;
edfda783
AR
1574 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1575 isQuestion: isQ];
c96169a0
AR
1576 {
1577 int specpdl_count = SPECPDL_INDEX ();
1578 record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1579 popup_activated_flag = 1;
1580 tem = [dialog runDialogAt: p];
ba301db3 1581 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
c96169a0 1582 }
3175b12a 1583 UNBLOCK_INPUT;
c96169a0 1584
edfda783
AR
1585 return tem;
1586}
1587
1588
1589/* ==========================================================================
1590
1591 Popup Dialog: class implementation
1592
1593 ========================================================================== */
1594
1595@interface FlippedView : NSView
1596{
1597}
1598@end
1599
1600@implementation FlippedView
1601- (BOOL)isFlipped
1602{
1603 return YES;
1604}
1605@end
1606
1607@implementation EmacsDialogPanel
1608
1609#define SPACER 8.0
1610#define ICONSIZE 64.0
1611#define TEXTHEIGHT 20.0
1612#define MINCELLWIDTH 90.0
1613
1614- initWithContentRect: (NSRect)contentRect styleMask: (unsigned int)aStyle
1615 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1616{
1617 NSSize spacing = {SPACER, SPACER};
1618 NSRect area;
1619 char this_cmd_name[80];
facfbbbd 1620 id cell;
edfda783
AR
1621 static NSImageView *imgView;
1622 static FlippedView *contentView;
1623
1624 if (imgView == nil)
1625 {
1626 NSImage *img;
1627 area.origin.x = 3*SPACER;
1628 area.origin.y = 2*SPACER;
1629 area.size.width = ICONSIZE;
1630 area.size.height= ICONSIZE;
1631 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1632 [img setScalesWhenResized: YES];
1633 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1634 imgView = [[NSImageView alloc] initWithFrame: area];
1635 [imgView setImage: img];
1636 [imgView setEditable: NO];
1637 [img release];
1638 }
1639
1640 aStyle = NSTitledWindowMask;
1641 flag = YES;
1642 rows = 0;
1643 cols = 1;
1644 [super initWithContentRect: contentRect styleMask: aStyle
1645 backing: backingType defer: flag];
1646 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1647 [self setContentView: contentView];
1648
1649 [[self contentView] setAutoresizesSubviews: YES];
1650
1651 [[self contentView] addSubview: imgView];
1652 [self setTitle: @""];
1653
1654 area.origin.x += ICONSIZE+2*SPACER;
1655/* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1656 area.size.width = 400;
1657 area.size.height= TEXTHEIGHT;
1658 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1659 [[self contentView] addSubview: command];
1660 [command setStringValue: @"Emacs"];
1661 [command setDrawsBackground: NO];
1662 [command setBezeled: NO];
1663 [command setSelectable: NO];
1664 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1665
1666/* area.origin.x = ICONSIZE+2*SPACER;
1667 area.origin.y = TEXTHEIGHT + 2*SPACER;
1668 area.size.width = 400;
1669 area.size.height= 2;
1670 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1671 [[self contentView] addSubview: tem];
1672 [tem setTitlePosition: NSNoTitle];
1673 [tem setAutoresizingMask: NSViewWidthSizable];*/
1674
1675/* area.origin.x = ICONSIZE+2*SPACER; */
1676 area.origin.y += TEXTHEIGHT+SPACER;
1677 area.size.width = 400;
1678 area.size.height= TEXTHEIGHT;
1679 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1680 [[self contentView] addSubview: title];
1681 [title setDrawsBackground: NO];
1682 [title setBezeled: NO];
1683 [title setSelectable: NO];
1684 [title setFont: [NSFont systemFontOfSize: 11.0]];
1685
1686 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1687 [cell setBordered: NO];
1688 [cell setEnabled: NO];
1689 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1690 [cell setBezelStyle: NSRoundedBezelStyle];
1691
1692 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1693 mode: NSHighlightModeMatrix
1694 prototype: cell
1695 numberOfRows: 0
1696 numberOfColumns: 1];
1697 [[self contentView] addSubview: matrix];
1698 [matrix release];
1699 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1700 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1701 [matrix setIntercellSpacing: spacing];
1702
1703 [self setOneShot: YES];
1704 [self setReleasedWhenClosed: YES];
1705 [self setHidesOnDeactivate: YES];
1706 return self;
1707}
1708
1709
1710- (BOOL)windowShouldClose: (id)sender
1711{
facfbbbd 1712 [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
edfda783
AR
1713 return NO;
1714}
1715
1716
1717void process_dialog (id window, Lisp_Object list)
1718{
1719 Lisp_Object item;
1720 int row = 0;
1721
1722 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1723 {
1724 item = XCAR (list);
1725 if (XTYPE (item) == Lisp_String)
1726 {
86fa089e 1727 [window addString: SDATA (item) row: row++];
edfda783
AR
1728 }
1729 else if (XTYPE (item) == Lisp_Cons)
1730 {
86fa089e 1731 [window addButton: SDATA (XCAR (item))
edfda783
AR
1732 value: XCDR (item) row: row++];
1733 }
1734 else if (NILP (item))
1735 {
1736 [window addSplit];
1737 row = 0;
1738 }
1739 }
1740}
1741
1742
1743- addButton: (char *)str value: (Lisp_Object)val row: (int)row
1744{
1745 id cell;
1746
1747 if (row >= rows)
1748 {
1749 [matrix addRow];
1750 rows++;
1751 }
1752 cell = [matrix cellAtRow: row column: cols-1];
1753 [cell setTarget: self];
1754 [cell setAction: @selector (clicked: )];
1755 [cell setTitle: [NSString stringWithUTF8String: str]];
facfbbbd 1756 [cell setTag: XHASH (val)]; // FIXME: BIG UGLY HACK!!
edfda783
AR
1757 [cell setBordered: YES];
1758 [cell setEnabled: YES];
1759
1760 return self;
1761}
1762
1763
1764- addString: (char *)str row: (int)row
1765{
1766 id cell;
1767
1768 if (row >= rows)
1769 {
1770 [matrix addRow];
1771 rows++;
1772 }
1773 cell = [matrix cellAtRow: row column: cols-1];
1774 [cell setTitle: [NSString stringWithUTF8String: str]];
1775 [cell setBordered: YES];
1776 [cell setEnabled: NO];
1777
1778 return self;
1779}
1780
1781
1782- addSplit
1783{
1784 [matrix addColumn];
1785 cols++;
1786 return self;
1787}
1788
1789
1790- clicked: sender
1791{
1792 NSArray *sellist = nil;
facfbbbd 1793 EMACS_INT seltag;
edfda783
AR
1794
1795 sellist = [sender selectedCells];
1796 if ([sellist count]<1)
1797 return self;
1798
facfbbbd 1799 seltag = [[sellist objectAtIndex: 0] tag];
4e622592 1800 if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
edfda783
AR
1801 [NSApp stopModalWithCode: seltag];
1802 return self;
1803}
1804
1805
1806- initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1807{
1808 Lisp_Object head;
1809 [super init];
1810
1811 if (XTYPE (contents) == Lisp_Cons)
1812 {
1813 head = Fcar (contents);
1814 process_dialog (self, Fcdr (contents));
1815 }
1816 else
1817 head = contents;
1818
1819 if (XTYPE (head) == Lisp_String)
1820 [title setStringValue:
86fa089e 1821 [NSString stringWithUTF8String: SDATA (head)]];
edfda783
AR
1822 else if (isQ == YES)
1823 [title setStringValue: @"Question"];
1824 else
1825 [title setStringValue: @"Information"];
1826
1827 {
1828 int i;
1829 NSRect r, s, t;
1830
1831 if (cols == 1 && rows > 1) /* Never told where to split */
1832 {
1833 [matrix addColumn];
1834 for (i = 0; i<rows/2; i++)
1835 {
1836 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1837 atRow: i column: 1];
1838 [matrix removeRow: (rows+1)/2];
1839 }
1840 }
1841
1842 [matrix sizeToFit];
1843 {
1844 NSSize csize = [matrix cellSize];
1845 if (csize.width < MINCELLWIDTH)
1846 {
1847 csize.width = MINCELLWIDTH;
1848 [matrix setCellSize: csize];
1849 [matrix sizeToCells];
1850 }
1851 }
1852
1853 [title sizeToFit];
1854 [command sizeToFit];
1855
1856 t = [matrix frame];
1857 r = [title frame];
1858 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1859 {
1860 t.origin.x = r.origin.x;
1861 t.size.width = r.size.width;
1862 }
1863 r = [command frame];
1864 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1865 {
1866 t.origin.x = r.origin.x;
1867 t.size.width = r.size.width;
1868 }
1869
1870 r = [self frame];
1871 s = [(NSView *)[self contentView] frame];
1872 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1873 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1874 [self setFrame: r display: NO];
1875 }
1876
1877 return self;
1878}
1879
1880
1881- (void)dealloc
1882{
1883 { [super dealloc]; return; };
1884}
1885
1886
1887- (Lisp_Object)runDialogAt: (NSPoint)p
1888{
edfda783 1889 int ret;
641d87f5 1890 extern EMACS_TIME timer_check (int do_it_now); /* TODO: add to a header */
edfda783 1891
ba301db3 1892 /* initiate a session that will be ended by pop_down_menu */
3175b12a 1893 popupSession = [NSApp beginModalSessionForWindow: self];
c96169a0 1894 while (popup_activated_flag
3175b12a
AR
1895 && (ret = [NSApp runModalSession: popupSession])
1896 == NSRunContinuesResponse)
edfda783 1897 {
3175b12a
AR
1898 /* Run this for timers.el, indep of atimers; might not return.
1899 TODO: use return value to avoid calling every iteration. */
1900 timer_check (1);
1901 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
edfda783 1902 }
edfda783 1903
3175b12a 1904 { /* FIXME: BIG UGLY HACK!!! */
facfbbbd
SM
1905 Lisp_Object tmp;
1906 *(EMACS_INT*)(&tmp) = ret;
1907 return tmp;
1908 }
edfda783
AR
1909}
1910
1911@end
1912
1913
edfda783
AR
1914/* ==========================================================================
1915
1916 Lisp definitions
1917
1918 ========================================================================== */
1919
1920DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
eb6f7ed0 1921 doc: /* Cause the NS menu to be re-calculated. */)
edfda783
AR
1922 ()
1923{
1924 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1925 return Qnil;
1926}
1927
1928
1929DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
eb6f7ed0
CY
1930 doc: /* Pop up a deck-of-cards menu and return user's selection.
1931POSITION is a position specification. This is either a mouse button event
1932or a list ((XOFFSET YOFFSET) WINDOW)
1933where XOFFSET and YOFFSET are positions in pixels from the top left
1934corner of WINDOW. (WINDOW may be a window or a frame object.)
1935This controls the position of the top left of the menu as a whole.
1936If POSITION is t, it means to use the current mouse position.
1937
1938MENU is a specifier for a menu. For the simplest case, MENU is a keymap.
1939The menu items come from key bindings that have a menu string as well as
1940a definition; actually, the \"definition\" in such a key binding looks like
1941\(STRING . REAL-DEFINITION). To give the menu a title, put a string into
1942the keymap as a top-level element.
1943
1944If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.
1945Otherwise, REAL-DEFINITION should be a valid key binding definition.
1946
1947You can also use a list of keymaps as MENU.
1948 Then each keymap makes a separate pane.
1949
1950When MENU is a keymap or a list of keymaps, the return value is the
1951list of events corresponding to the user's choice. Note that
1952`x-popup-menu' does not actually execute the command bound to that
1953sequence of events.
1954
1955Alternatively, you can specify a menu of multiple panes
1956 with a list of the form (TITLE PANE1 PANE2...),
1957where each pane is a list of form (TITLE ITEM1 ITEM2...).
1958Each ITEM is normally a cons cell (STRING . VALUE);
1959but a string can appear as an item--that makes a nonselectable line
1960in the menu.
1961With this form of menu, the return value is VALUE from the chosen item. */)
edfda783
AR
1962 (position, menu)
1963 Lisp_Object position, menu;
1964{
1965 return ns_popup_menu (position, menu);
1966}
1967
1968
1969DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1970 doc: /* Pop up a dialog box and return user's selection.
1971POSITION specifies which frame to use.
1972This is normally a mouse button event or a window or frame.
1973If POSITION is t, it means to use the frame the mouse is on.
1974The dialog box appears in the middle of the specified frame.
1975
1976CONTENTS specifies the alternatives to display in the dialog box.
1977It is a list of the form (DIALOG ITEM1 ITEM2...).
1978Each ITEM is a cons cell (STRING . VALUE).
1979The return value is VALUE from the chosen item.
1980
1981An ITEM may also be just a string--that makes a nonselectable item.
1982An ITEM may also be nil--that means to put all preceding items
1983on the left of the dialog box and all following items on the right.
1984\(By default, approximately half appear on each side.)
1985
1986If HEADER is non-nil, the frame title for the box is "Information",
1987otherwise it is "Question".
1988
1989If the user gets rid of the dialog box without making a valid choice,
1990for instance using the window manager, then this produces a quit and
1991`x-popup-dialog' does not return. */)
1992 (position, contents, header)
1993 Lisp_Object position, contents, header;
1994{
1995 return ns_popup_dialog (position, contents, header);
1996}
1997
07b87a10
AR
1998DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1999 doc: /* Return t if a menu or popup dialog is active. */)
2000 ()
2001{
2002 return popup_activated () ? Qt : Qnil;
2003}
edfda783
AR
2004
2005/* ==========================================================================
2006
2007 Lisp interface declaration
2008
2009 ========================================================================== */
2010
2011void
2012syms_of_nsmenu ()
2013{
2014 defsubr (&Sx_popup_menu);
2015 defsubr (&Sx_popup_dialog);
2016 defsubr (&Sns_reset_menu);
07b87a10 2017 defsubr (&Smenu_or_popup_active_p);
edfda783
AR
2018 staticpro (&menu_items);
2019 menu_items = Qnil;
2020
2021 Qdebug_on_next_call = intern ("debug-on-next-call");
2022 staticpro (&Qdebug_on_next_call);
2023}
0ae1e5e5 2024
2f8e74bb 2025// arch-tag: 75773656-52e5-4c44-a398-47bd87b32619