Add bug reference.
[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];
c7cef62d
AR
639 if (keyEquivModMask == 0)
640 title = [title stringByAppendingFormat: @" (%@)", keyEq];
edfda783
AR
641
642 item = [self addItemWithTitle: (NSString *)title
643 action: @selector (menuDown:)
644 keyEquivalent: keyEq];
c7cef62d 645 [item setKeyEquivalentModifierMask: keyEquivModMask];
edfda783
AR
646
647 [item setEnabled: wv->enabled];
648
649 /* Draw radio buttons and tickboxes */
650 if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
651 wv->button_type == BUTTON_TYPE_RADIO))
652 [item setState: NSOnState];
653 else
654 [item setState: NSOffState];
655
656 [item setTag: (int)wv->call_data];
657 }
658
659 return item;
660}
661
662
663/* convenience */
664-(void) clear
665{
666 int n;
667
668 for (n = [self numberOfItems]-1; n >= 0; n--)
669 {
670 NSMenuItem *item = [self itemAtIndex: n];
671 NSString *title = [item title];
672 if (([title length] == 0 || [@"Apple" isEqualToString: title])
673 && ![item isSeparatorItem])
674 continue;
675 [self removeItemAtIndex: n];
676 }
677}
678
679
680- (void)fillWithWidgetValue: (void *)wvptr
681{
682 widget_value *wv = (widget_value *)wvptr;
683
684 /* clear existing contents */
685 [self setMenuChangedMessagesEnabled: NO];
686 [self clear];
687
688 /* add new contents */
689 for (; wv != NULL; wv = wv->next)
690 {
15034960 691 NSMenuItem *item = [self addItemWithWidgetValue: wv];
edfda783
AR
692
693 if (wv->contents)
694 {
c96169a0 695 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
edfda783
AR
696
697 [self setSubmenu: submenu forItem: item];
698 [submenu fillWithWidgetValue: wv->contents];
699 [submenu release];
700 [item setAction: nil];
701 }
702 }
703
704 [self setMenuChangedMessagesEnabled: YES];
705#ifdef NS_IMPL_GNUSTEP
706 if ([[self window] isVisible])
707 [self sizeToFit];
708#else
709 if ([self supermenu] == nil)
710 [self sizeToFit];
711#endif
712}
713
714
715/* adds an empty submenu and returns it */
716- (EmacsMenu *)addSubmenuWithTitle: (char *)title forFrame: (struct frame *)f
717{
718 NSString *titleStr = [NSString stringWithUTF8String: title];
15034960
AR
719 NSMenuItem *item = [self addItemWithTitle: titleStr
720 action: nil /*@selector (menuDown:) */
721 keyEquivalent: @""];
edfda783
AR
722 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
723 [self setSubmenu: submenu forItem: item];
724 [submenu release];
725 return submenu;
726}
727
728/* run a menu in popup mode */
729- (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
730 keymaps: (int)keymaps
731{
732 EmacsView *view = FRAME_NS_VIEW (f);
733/* p = [view convertPoint:p fromView: nil]; */
734 p.y = NSHeight ([view frame]) - p.y;
735 NSEvent *e = [[view window] currentEvent];
736 NSEvent *event = [NSEvent mouseEventWithType: NSRightMouseDown
737 location: p
738 modifierFlags: 0
739 timestamp: [e timestamp]
740 windowNumber: [[view window] windowNumber]
741 context: [e context]
742 eventNumber: 0/*[e eventNumber] */
743 clickCount: 1
744 pressure: 0];
745 long retVal;
746
747 context_menu_value = -1;
748 [NSMenu popUpContextMenu: self withEvent: event forView: view];
749 retVal = context_menu_value;
750 context_menu_value = 0;
facfbbbd
SM
751 return retVal > 0
752 ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
753 : Qnil;
edfda783
AR
754}
755
756@end /* EmacsMenu */
757
758
759
760/* ==========================================================================
761
762 Context Menu: implementing functions
763
764 ========================================================================== */
765
766static Lisp_Object
767cleanup_popup_menu (Lisp_Object arg)
768{
769 discard_menu_items ();
770 return Qnil;
771}
772
773
774static Lisp_Object
775ns_popup_menu (Lisp_Object position, Lisp_Object menu)
776{
777 EmacsMenu *pmenu;
778 struct frame *f = NULL;
779 NSPoint p;
780 Lisp_Object window, x, y, tem, keymap, title;
781 struct gcpro gcpro1;
782 int specpdl_count = SPECPDL_INDEX (), specpdl_count2;
783 char *error_name = NULL;
784 int keymaps = 0;
785 widget_value *wv, *first_wv = 0;
786
787 NSTRACE (ns_popup_menu);
788
789 if (!NILP (position))
790 {
791 check_ns ();
792
793 if (EQ (position, Qt)
794 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
795 || EQ (XCAR (position), Qtool_bar))))
796 {
797 /* Use the mouse's current position. */
798 struct frame *new_f = SELECTED_FRAME ();
799
800 if (FRAME_TERMINAL (new_f)->mouse_position_hook)
801 (*FRAME_TERMINAL (new_f)->mouse_position_hook)
802 (&new_f, 0, 0, 0, &x, &y, 0);
803 if (new_f != 0)
804 XSETFRAME (window, new_f);
805 else
806 {
807 window = selected_window;
808 x = make_number (0);
809 y = make_number (0);
810 }
811 }
812 else
813 {
814 CHECK_CONS (position);
815 tem = Fcar (position);
816 if (XTYPE (tem) == Lisp_Cons)
817 {
818 window = Fcar (Fcdr (position));
819 x = Fcar (tem);
820 y = Fcar (Fcdr (tem));
821 }
822 else
823 {
824 tem = Fcar (Fcdr (position));
825 window = Fcar (tem);
826 tem = Fcar (Fcdr (Fcdr (tem)));
827 x = Fcar (tem);
828 y = Fcdr (tem);
829 }
830 }
831
832 CHECK_NUMBER (x);
833 CHECK_NUMBER (y);
834
835 if (FRAMEP (window))
836 {
837 f = XFRAME (window);
838
839 p.x = 0;
840 p.y = 0;
841 }
842 else
843 {
844 struct window *win = XWINDOW (window);
845 CHECK_LIVE_WINDOW (window);
846 f = XFRAME (WINDOW_FRAME (win));
847 p.x = FRAME_COLUMN_WIDTH (f) * WINDOW_LEFT_EDGE_COL (win);
848 p.y = FRAME_LINE_HEIGHT (f) * WINDOW_TOP_EDGE_LINE (win);
849 }
850
851 p.x += XINT (x); p.y += XINT (y);
852
853 XSETFRAME (Vmenu_updating_frame, f);
854 }
855 else
856 { /* no position given */
df2142db 857 /* FIXME: if called during dump, we need to stop precomputation of
edfda783
AR
858 key equivalents (see below) because the keydefs in ns-win.el have
859 not been loaded yet. */
860 if (noninteractive)
861 return Qnil;
862 Vmenu_updating_frame = Qnil;
863 }
864
865 /* now parse the lisp menus */
866 record_unwind_protect (unuse_menu_items, Qnil);
867 title = Qnil;
868 GCPRO1 (title);
869
870 /* Decode the menu items from what was specified. */
871
872 keymap = get_keymap (menu, 0, 0);
873 if (CONSP (keymap))
874 {
875 /* We were given a keymap. Extract menu info from the keymap. */
876 Lisp_Object prompt;
877
878 /* Extract the detailed info to make one pane. */
879 keymap_panes (&menu, 1, NILP (position));
880
881 /* Search for a string appearing directly as an element of the keymap.
882 That string is the title of the menu. */
883 prompt = Fkeymap_prompt (keymap);
884 title = NILP (prompt) ? build_string ("Select") : prompt;
885
886 /* Make that be the pane title of the first pane. */
887 if (!NILP (prompt) && menu_items_n_panes >= 0)
888 XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt;
889
890 keymaps = 1;
891 }
892 else if (CONSP (menu) && KEYMAPP (XCAR (menu)))
893 {
894 /* We were given a list of keymaps. */
895 int nmaps = XFASTINT (Flength (menu));
896 Lisp_Object *maps
897 = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object));
898 int i;
899
900 title = Qnil;
901
902 /* The first keymap that has a prompt string
903 supplies the menu title. */
904 for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem))
905 {
906 Lisp_Object prompt;
907
908 maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0);
909
910 prompt = Fkeymap_prompt (keymap);
911 if (NILP (title) && !NILP (prompt))
912 title = prompt;
913 }
914
915 /* Extract the detailed info to make one pane. */
916 keymap_panes (maps, nmaps, NILP (position));
917
918 /* Make the title be the pane title of the first pane. */
919 if (!NILP (title) && menu_items_n_panes >= 0)
920 XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title;
921
922 keymaps = 1;
923 }
924 else
925 {
926 /* We were given an old-fashioned menu. */
927 title = Fcar (menu);
928 CHECK_STRING (title);
929
930 list_of_panes (Fcdr (menu));
931
932 keymaps = 0;
933 }
934
935 unbind_to (specpdl_count, Qnil);
936
937 /* If no position given, that was a signal to just precompute and cache
938 key equivalents, which was a side-effect of what we just did. */
939 if (NILP (position))
940 {
941 discard_menu_items ();
942 UNGCPRO;
943 return Qnil;
944 }
945
946 record_unwind_protect (cleanup_popup_menu, Qnil);
947 BLOCK_INPUT;
948
949 /* now parse stage 2 as in ns_update_menubar */
950 wv = xmalloc_widget_value ();
951 wv->name = "contextmenu";
952 wv->value = 0;
953 wv->enabled = 1;
954 wv->button_type = BUTTON_TYPE_NONE;
955 wv->help = Qnil;
956 first_wv = wv;
957
958 specpdl_count2 = SPECPDL_INDEX ();
959
960#if 0
df2142db 961 /* FIXME: a couple of one-line differences prevent reuse */
edfda783
AR
962 wv = digest_single_submenu (0, menu_items_used, Qnil);
963#else
964 {
965 widget_value *save_wv = 0, *prev_wv = 0;
966 widget_value **submenu_stack
967 = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
968/* Lisp_Object *subprefix_stack
969 = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
970 int submenu_depth = 0;
971 int first_pane = 1;
972 int i;
973
974 /* Loop over all panes and items, filling in the tree. */
975 i = 0;
976 while (i < menu_items_used)
977 {
978 if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
979 {
980 submenu_stack[submenu_depth++] = save_wv;
981 save_wv = prev_wv;
982 prev_wv = 0;
983 first_pane = 1;
984 i++;
985 }
986 else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
987 {
988 prev_wv = save_wv;
989 save_wv = submenu_stack[--submenu_depth];
990 first_pane = 0;
991 i++;
992 }
993 else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
994 && submenu_depth != 0)
995 i += MENU_ITEMS_PANE_LENGTH;
996 /* Ignore a nil in the item list.
997 It's meaningful only for dialog boxes. */
998 else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
999 i += 1;
1000 else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
1001 {
1002 /* Create a new pane. */
1003 Lisp_Object pane_name, prefix;
1004 char *pane_string;
1005
1006 pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
1007 prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
1008
1009#ifndef HAVE_MULTILINGUAL_MENU
1010 if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
1011 {
1012 pane_name = ENCODE_MENU_STRING (pane_name);
1013 ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
1014 }
1015#endif
1016 pane_string = (NILP (pane_name)
1017 ? "" : (char *) SDATA (pane_name));
1018 /* If there is just one top-level pane, put all its items directly
1019 under the top-level menu. */
1020 if (menu_items_n_panes == 1)
1021 pane_string = "";
1022
1023 /* If the pane has a meaningful name,
1024 make the pane a top-level menu item
1025 with its items as a submenu beneath it. */
1026 if (!keymaps && strcmp (pane_string, ""))
1027 {
1028 wv = xmalloc_widget_value ();
1029 if (save_wv)
1030 save_wv->next = wv;
1031 else
1032 first_wv->contents = wv;
1033 wv->name = pane_string;
1034 if (keymaps && !NILP (prefix))
1035 wv->name++;
1036 wv->value = 0;
1037 wv->enabled = 1;
1038 wv->button_type = BUTTON_TYPE_NONE;
1039 wv->help = Qnil;
1040 save_wv = wv;
1041 prev_wv = 0;
1042 }
1043 else if (first_pane)
1044 {
1045 save_wv = wv;
1046 prev_wv = 0;
1047 }
1048 first_pane = 0;
1049 i += MENU_ITEMS_PANE_LENGTH;
1050 }
1051 else
1052 {
1053 /* Create a new item within current pane. */
1054 Lisp_Object item_name, enable, descrip, def, type, selected, help;
1055 item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
1056 enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
1057 descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
1058 def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
1059 type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
1060 selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
1061 help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
1062
1063#ifndef HAVE_MULTILINGUAL_MENU
1064 if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
1065 {
1066 item_name = ENCODE_MENU_STRING (item_name);
1067 ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
1068 }
1069
1070 if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
1071 {
1072 descrip = ENCODE_MENU_STRING (descrip);
1073 ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
1074 }
1075#endif /* not HAVE_MULTILINGUAL_MENU */
1076
1077 wv = xmalloc_widget_value ();
1078 if (prev_wv)
1079 prev_wv->next = wv;
1080 else
1081 save_wv->contents = wv;
1082 wv->name = (char *) SDATA (item_name);
1083 if (!NILP (descrip))
1084 wv->key = (char *) SDATA (descrip);
1085 wv->value = 0;
1086 /* If this item has a null value,
1087 make the call_data null so that it won't display a box
1088 when the mouse is on it. */
facfbbbd
SM
1089 wv->call_data
1090 = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
edfda783
AR
1091 wv->enabled = !NILP (enable);
1092
1093 if (NILP (type))
1094 wv->button_type = BUTTON_TYPE_NONE;
1095 else if (EQ (type, QCtoggle))
1096 wv->button_type = BUTTON_TYPE_TOGGLE;
1097 else if (EQ (type, QCradio))
1098 wv->button_type = BUTTON_TYPE_RADIO;
1099 else
1100 abort ();
1101
1102 wv->selected = !NILP (selected);
1103
1104 if (! STRINGP (help))
1105 help = Qnil;
1106
1107 wv->help = help;
1108
1109 prev_wv = wv;
1110
1111 i += MENU_ITEMS_ITEM_LENGTH;
1112 }
1113 }
1114 }
1115#endif
1116
1117 if (!NILP (title))
1118 {
1119 widget_value *wv_title = xmalloc_widget_value ();
1120 widget_value *wv_sep = xmalloc_widget_value ();
1121
1122 /* Maybe replace this separator with a bitmap or owner-draw item
1123 so that it looks better. Having two separators looks odd. */
1124 wv_sep->name = "--";
1125 wv_sep->next = first_wv->contents;
1126 wv_sep->help = Qnil;
1127
1128#ifndef HAVE_MULTILINGUAL_MENU
1129 if (STRING_MULTIBYTE (title))
1130 title = ENCODE_MENU_STRING (title);
1131#endif
1132
1133 wv_title->name = (char *) SDATA (title);
15034960 1134 wv_title->enabled = NO;
edfda783
AR
1135 wv_title->button_type = BUTTON_TYPE_NONE;
1136 wv_title->help = Qnil;
1137 wv_title->next = wv_sep;
1138 first_wv->contents = wv_title;
1139 }
1140
1141 pmenu = [[EmacsMenu alloc] initWithTitle:
1142 [NSString stringWithUTF8String: SDATA (title)]];
1143 [pmenu fillWithWidgetValue: first_wv->contents];
1144 free_menubar_widget_value_tree (first_wv);
1145 unbind_to (specpdl_count2, Qnil);
1146
07b87a10 1147 popup_activated_flag = 1;
edfda783 1148 tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
07b87a10 1149 popup_activated_flag = 0;
edfda783
AR
1150 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1151
1152 UNBLOCK_INPUT;
07b87a10 1153 discard_menu_items ();
edfda783
AR
1154 unbind_to (specpdl_count, Qnil);
1155 UNGCPRO;
1156
1157 if (error_name) error (error_name);
1158 return tem;
1159}
1160
1161
1162
1163
1164/* ==========================================================================
1165
1166 Toolbar: externally-called functions
1167
1168 ========================================================================== */
1169
1170void
1171free_frame_tool_bar (FRAME_PTR f)
1172/* --------------------------------------------------------------------------
1173 Under NS we just hide the toolbar until it might be needed again.
1174 -------------------------------------------------------------------------- */
1175{
1176 [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1177}
1178
1179void
1180update_frame_tool_bar (FRAME_PTR f)
1181/* --------------------------------------------------------------------------
1182 Update toolbar contents
1183 -------------------------------------------------------------------------- */
1184{
1185 int i;
1186 EmacsToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar];
1187
edfda783
AR
1188 [toolbar clearActive];
1189
1190 /* update EmacsToolbar as in GtkUtils, build items list */
1191 for (i = 0; i < f->n_tool_bar_items; ++i)
1192 {
1193#define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1194 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1195
1196 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1197 BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1198 int idx;
1199 int img_id;
1200 struct image *img;
1201 Lisp_Object image;
1202 Lisp_Object helpObj;
1203 char *helpText;
1204
1205 /* If image is a vector, choose the image according to the
1206 button state. */
1207 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1208 if (VECTORP (image))
1209 {
1210 /* NS toolbar auto-computes disabled and selected images */
1211 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1212 xassert (ASIZE (image) >= idx);
1213 image = AREF (image, idx);
1214 }
1215 else
1216 {
1217 idx = -1;
1218 }
1219 /* Ignore invalid image specifications. */
1220 if (!valid_image_p (image))
1221 {
1222 NSLog (@"Invalid image for toolbar item");
1223 continue;
1224 }
1225
1226 img_id = lookup_image (f, image);
1227 img = IMAGE_FROM_ID (f, img_id);
1228 prepare_image_for_display (f, img);
1229
1230 if (img->load_failed_p || img->pixmap == nil)
1231 {
1232 NSLog (@"Could not prepare toolbar image for display.");
1233 continue;
1234 }
1235
1236 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1237 if (NILP (helpObj))
1238 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1239 helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1240
1241 [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1242 enabled: enabled_p];
1243#undef TOOLPROP
1244 }
1245
1246 if (![toolbar isVisible])
1247 [toolbar setVisible: YES];
1248
1249 if ([toolbar changed])
1250 {
1251 /* inform app that toolbar has changed */
1252 NSDictionary *dict = [toolbar configurationDictionary];
1253 NSMutableDictionary *newDict = [dict mutableCopy];
1254 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1255 NSObject *key;
1256 while ((key = [keys nextObject]) != nil)
1257 {
1258 NSObject *val = [dict objectForKey: key];
1259 if ([val isKindOfClass: [NSArray class]])
1260 {
1261 [newDict setObject:
1262 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1263 forKey: key];
1264 break;
1265 }
1266 }
1267 [toolbar setConfigurationFromDictionary: newDict];
1268 [newDict release];
1269 }
1270
1271}
1272
1273
1274/* ==========================================================================
1275
1276 Toolbar: class implementation
1277
1278 ========================================================================== */
1279
1280@implementation EmacsToolbar
1281
1282- initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1283{
1284 self = [super initWithIdentifier: identifier];
1285 emacsView = view;
1286 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1287 [self setSizeMode: NSToolbarSizeModeSmall];
1288 [self setDelegate: self];
1289 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1290 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1291 prevEnablement = enablement = 0L;
1292 return self;
1293}
1294
1295- (void)dealloc
1296{
1297 [prevIdentifiers release];
1298 [activeIdentifiers release];
1299 [identifierToItem release];
1300 [super dealloc];
1301}
1302
1303- (void) clearActive
1304{
1305 [prevIdentifiers release];
1306 prevIdentifiers = [activeIdentifiers copy];
1307 [activeIdentifiers removeAllObjects];
1308 prevEnablement = enablement;
1309 enablement = 0L;
1310}
1311
1312- (BOOL) changed
1313{
1314 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1315 enablement == prevEnablement ? NO : YES;
1316}
1317
1318- (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1319 helpText: (char *)help enabled: (BOOL)enabled
1320{
1321 /* 1) come up w/identifier */
facfbbbd
SM
1322 NSString *identifier
1323 = [NSString stringWithFormat: @"%u", [img hash]];
edfda783
AR
1324
1325 /* 2) create / reuse item */
1326 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1327 if (item == nil)
1328 {
1329 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1330 autorelease];
1331 [item setImage: img];
1332 [item setToolTip: [NSString stringWithCString: help]];
1333 [item setTarget: emacsView];
1334 [item setAction: @selector (toolbarClicked:)];
1335 }
1336
1337 [item setTag: idx];
1338 [item setEnabled: enabled];
1339
1340 /* 3) update state */
1341 [identifierToItem setObject: item forKey: identifier];
1342 [activeIdentifiers addObject: identifier];
1343 enablement = (enablement << 1) | (enabled == YES);
1344}
1345
1346/* This overrides super's implementation, which automatically sets
1347 all items to enabled state (for some reason). */
1348- (void)validateVisibleItems { }
1349
1350
1351/* delegate methods */
1352
1353- (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1354 itemForItemIdentifier: (NSString *)itemIdentifier
1355 willBeInsertedIntoToolbar: (BOOL)flag
1356{
1357 /* look up NSToolbarItem by identifier and return... */
1358 return [identifierToItem objectForKey: itemIdentifier];
1359}
1360
1361- (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1362{
1363 /* return entire set.. */
1364 return activeIdentifiers;
1365}
1366
1367/* for configuration palette (not yet supported) */
1368- (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1369{
1370 /* return entire set... */
1371 return [identifierToItem allKeys];
1372}
1373
1374/* optional and unneeded */
1375/* - toolbarWillAddItem: (NSNotification *)notification { } */
1376/* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1377/* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1378
1379@end /* EmacsToolbar */
1380
1381
1382
1383/* ==========================================================================
1384
1385 Tooltip: class implementation
1386
1387 ========================================================================== */
1388
1389/* Needed because NeXTstep does not provide enough control over tooltip
1390 display. */
1391@implementation EmacsTooltip
1392
1393- init
1394{
1395 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1396 blue: 0.792 alpha: 0.95];
1397 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1398 NSFont *sfont = [font screenFont];
1399 int height = [sfont ascender] - [sfont descender];
1400/*[font boundingRectForFont].size.height; */
1401 NSRect r = NSMakeRect (0, 0, 100, height+6);
1402
1403 textField = [[NSTextField alloc] initWithFrame: r];
1404 [textField setFont: font];
1405 [textField setBackgroundColor: col];
1406
1407 [textField setEditable: NO];
1408 [textField setSelectable: NO];
1409 [textField setBordered: YES];
1410 [textField setBezeled: YES];
1411 [textField setDrawsBackground: YES];
1412
1413 win = [[NSWindow alloc]
1414 initWithContentRect: [textField frame]
1415 styleMask: 0
1416 backing: NSBackingStoreBuffered
1417 defer: YES];
1418 [win setReleasedWhenClosed: NO];
1419 [win setDelegate: self];
1420 [[win contentView] addSubview: textField];
1421/* [win setBackgroundColor: col]; */
1422 [win setOpaque: NO];
1423
1424 return self;
1425}
1426
1427- (void) dealloc
1428{
1429 [win close];
1430 [win release];
1431 [textField release];
1432 [super dealloc];
1433}
1434
1435- (void) setText: (char *)text
1436{
1437 NSString *str = [NSString stringWithUTF8String: text];
1438 NSRect r = [textField frame];
1439 r.size.width = [[[textField font] screenFont] widthOfString: str] + 8;
1440 [textField setFrame: r];
1441 [textField setStringValue: str];
1442}
1443
1444- (void) showAtX: (int)x Y: (int)y for: (int)seconds
1445{
1446 NSRect wr = [win frame];
1447
1448 wr.origin = NSMakePoint (x, y);
1449 wr.size = [textField frame].size;
1450
1451 [win setFrame: wr display: YES];
1452 [win orderFront: self];
1453 [win display];
1454 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1455 selector: @selector (hide)
1456 userInfo: nil repeats: NO];
1457 [timer retain];
1458}
1459
1460- (void) hide
1461{
1462 [win close];
1463 if (timer != nil)
1464 {
1465 if ([timer isValid])
1466 [timer invalidate];
1467 [timer release];
1468 timer = nil;
1469 }
1470}
1471
1472- (BOOL) isActive
1473{
1474 return timer != nil;
1475}
1476
1477- (NSRect) frame
1478{
1479 return [textField frame];
1480}
1481
1482@end /* EmacsTooltip */
1483
1484
1485
1486/* ==========================================================================
1487
1488 Popup Dialog: implementing functions
1489
1490 ========================================================================== */
1491
c96169a0
AR
1492
1493static Lisp_Object
1494pop_down_menu (Lisp_Object arg)
1495{
1496 struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1497 popup_activated_flag = 0;
1498 BLOCK_INPUT;
3175b12a 1499 [NSApp endModalSession: popupSession];
c96169a0
AR
1500 [((EmacsDialogPanel *) (p->pointer)) close];
1501 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1502 UNBLOCK_INPUT;
1503 return Qnil;
1504}
1505
1506
edfda783
AR
1507Lisp_Object
1508ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1509{
1510 id dialog;
1511 Lisp_Object window, tem;
1512 struct frame *f;
1513 NSPoint p;
1514 BOOL isQ;
1515
1516 NSTRACE (x-popup-dialog);
1517
1518 check_ns ();
1519
1520 isQ = NILP (header);
1521
8612b71a
AR
1522 if (EQ (position, Qt)
1523 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1524 || EQ (XCAR (position), Qtool_bar))))
edfda783
AR
1525 {
1526 window = selected_window;
1527 }
1528 else if (CONSP (position))
1529 {
1530 Lisp_Object tem;
1531 tem = Fcar (position);
1532 if (XTYPE (tem) == Lisp_Cons)
1533 window = Fcar (Fcdr (position));
1534 else
1535 {
1536 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1537 window = Fcar (tem); /* POSN_WINDOW (tem) */
1538 }
1539 }
8612b71a 1540 else if (WINDOWP (position) || FRAMEP (position))
edfda783
AR
1541 {
1542 window = position;
1543 }
1544 else
8612b71a
AR
1545 window = Qnil;
1546
edfda783
AR
1547 if (FRAMEP (window))
1548 f = XFRAME (window);
8612b71a 1549 else if (WINDOWP (window))
edfda783
AR
1550 {
1551 CHECK_LIVE_WINDOW (window);
1552 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1553 }
8612b71a
AR
1554 else
1555 CHECK_WINDOW (window);
1556
edfda783
AR
1557 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1558 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
3175b12a
AR
1559
1560 BLOCK_INPUT;
edfda783
AR
1561 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1562 isQuestion: isQ];
c96169a0
AR
1563 {
1564 int specpdl_count = SPECPDL_INDEX ();
1565 record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1566 popup_activated_flag = 1;
1567 tem = [dialog runDialogAt: p];
1568 popup_activated_flag = 0;
1569 unbind_to (specpdl_count, Qnil);
1570 }
edfda783
AR
1571
1572 [dialog close];
edfda783 1573 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
3175b12a 1574 UNBLOCK_INPUT;
c96169a0 1575
edfda783
AR
1576 return tem;
1577}
1578
1579
1580/* ==========================================================================
1581
1582 Popup Dialog: class implementation
1583
1584 ========================================================================== */
1585
1586@interface FlippedView : NSView
1587{
1588}
1589@end
1590
1591@implementation FlippedView
1592- (BOOL)isFlipped
1593{
1594 return YES;
1595}
1596@end
1597
1598@implementation EmacsDialogPanel
1599
1600#define SPACER 8.0
1601#define ICONSIZE 64.0
1602#define TEXTHEIGHT 20.0
1603#define MINCELLWIDTH 90.0
1604
1605- initWithContentRect: (NSRect)contentRect styleMask: (unsigned int)aStyle
1606 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1607{
1608 NSSize spacing = {SPACER, SPACER};
1609 NSRect area;
1610 char this_cmd_name[80];
facfbbbd 1611 id cell;
edfda783
AR
1612 static NSImageView *imgView;
1613 static FlippedView *contentView;
1614
1615 if (imgView == nil)
1616 {
1617 NSImage *img;
1618 area.origin.x = 3*SPACER;
1619 area.origin.y = 2*SPACER;
1620 area.size.width = ICONSIZE;
1621 area.size.height= ICONSIZE;
1622 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1623 [img setScalesWhenResized: YES];
1624 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1625 imgView = [[NSImageView alloc] initWithFrame: area];
1626 [imgView setImage: img];
1627 [imgView setEditable: NO];
1628 [img release];
1629 }
1630
1631 aStyle = NSTitledWindowMask;
1632 flag = YES;
1633 rows = 0;
1634 cols = 1;
1635 [super initWithContentRect: contentRect styleMask: aStyle
1636 backing: backingType defer: flag];
1637 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1638 [self setContentView: contentView];
1639
1640 [[self contentView] setAutoresizesSubviews: YES];
1641
1642 [[self contentView] addSubview: imgView];
1643 [self setTitle: @""];
1644
1645 area.origin.x += ICONSIZE+2*SPACER;
1646/* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1647 area.size.width = 400;
1648 area.size.height= TEXTHEIGHT;
1649 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1650 [[self contentView] addSubview: command];
1651 [command setStringValue: @"Emacs"];
1652 [command setDrawsBackground: NO];
1653 [command setBezeled: NO];
1654 [command setSelectable: NO];
1655 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1656
1657/* area.origin.x = ICONSIZE+2*SPACER;
1658 area.origin.y = TEXTHEIGHT + 2*SPACER;
1659 area.size.width = 400;
1660 area.size.height= 2;
1661 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1662 [[self contentView] addSubview: tem];
1663 [tem setTitlePosition: NSNoTitle];
1664 [tem setAutoresizingMask: NSViewWidthSizable];*/
1665
1666/* area.origin.x = ICONSIZE+2*SPACER; */
1667 area.origin.y += TEXTHEIGHT+SPACER;
1668 area.size.width = 400;
1669 area.size.height= TEXTHEIGHT;
1670 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1671 [[self contentView] addSubview: title];
1672 [title setDrawsBackground: NO];
1673 [title setBezeled: NO];
1674 [title setSelectable: NO];
1675 [title setFont: [NSFont systemFontOfSize: 11.0]];
1676
1677 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1678 [cell setBordered: NO];
1679 [cell setEnabled: NO];
1680 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1681 [cell setBezelStyle: NSRoundedBezelStyle];
1682
1683 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1684 mode: NSHighlightModeMatrix
1685 prototype: cell
1686 numberOfRows: 0
1687 numberOfColumns: 1];
1688 [[self contentView] addSubview: matrix];
1689 [matrix release];
1690 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1691 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1692 [matrix setIntercellSpacing: spacing];
1693
1694 [self setOneShot: YES];
1695 [self setReleasedWhenClosed: YES];
1696 [self setHidesOnDeactivate: YES];
1697 return self;
1698}
1699
1700
1701- (BOOL)windowShouldClose: (id)sender
1702{
facfbbbd 1703 [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
edfda783
AR
1704 return NO;
1705}
1706
1707
1708void process_dialog (id window, Lisp_Object list)
1709{
1710 Lisp_Object item;
1711 int row = 0;
1712
1713 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1714 {
1715 item = XCAR (list);
1716 if (XTYPE (item) == Lisp_String)
1717 {
1718 [window addString: XSTRING (item)->data row: row++];
1719 }
1720 else if (XTYPE (item) == Lisp_Cons)
1721 {
1722 [window addButton: XSTRING (XCAR (item))->data
1723 value: XCDR (item) row: row++];
1724 }
1725 else if (NILP (item))
1726 {
1727 [window addSplit];
1728 row = 0;
1729 }
1730 }
1731}
1732
1733
1734- addButton: (char *)str value: (Lisp_Object)val row: (int)row
1735{
1736 id cell;
1737
1738 if (row >= rows)
1739 {
1740 [matrix addRow];
1741 rows++;
1742 }
1743 cell = [matrix cellAtRow: row column: cols-1];
1744 [cell setTarget: self];
1745 [cell setAction: @selector (clicked: )];
1746 [cell setTitle: [NSString stringWithUTF8String: str]];
facfbbbd 1747 [cell setTag: XHASH (val)]; // FIXME: BIG UGLY HACK!!
edfda783
AR
1748 [cell setBordered: YES];
1749 [cell setEnabled: YES];
1750
1751 return self;
1752}
1753
1754
1755- addString: (char *)str row: (int)row
1756{
1757 id cell;
1758
1759 if (row >= rows)
1760 {
1761 [matrix addRow];
1762 rows++;
1763 }
1764 cell = [matrix cellAtRow: row column: cols-1];
1765 [cell setTitle: [NSString stringWithUTF8String: str]];
1766 [cell setBordered: YES];
1767 [cell setEnabled: NO];
1768
1769 return self;
1770}
1771
1772
1773- addSplit
1774{
1775 [matrix addColumn];
1776 cols++;
1777 return self;
1778}
1779
1780
1781- clicked: sender
1782{
1783 NSArray *sellist = nil;
facfbbbd 1784 EMACS_INT seltag;
edfda783
AR
1785
1786 sellist = [sender selectedCells];
1787 if ([sellist count]<1)
1788 return self;
1789
facfbbbd 1790 seltag = [[sellist objectAtIndex: 0] tag];
4e622592 1791 if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
edfda783
AR
1792 [NSApp stopModalWithCode: seltag];
1793 return self;
1794}
1795
1796
1797- initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1798{
1799 Lisp_Object head;
1800 [super init];
1801
1802 if (XTYPE (contents) == Lisp_Cons)
1803 {
1804 head = Fcar (contents);
1805 process_dialog (self, Fcdr (contents));
1806 }
1807 else
1808 head = contents;
1809
1810 if (XTYPE (head) == Lisp_String)
1811 [title setStringValue:
1812 [NSString stringWithUTF8String: XSTRING (head)->data]];
1813 else if (isQ == YES)
1814 [title setStringValue: @"Question"];
1815 else
1816 [title setStringValue: @"Information"];
1817
1818 {
1819 int i;
1820 NSRect r, s, t;
1821
1822 if (cols == 1 && rows > 1) /* Never told where to split */
1823 {
1824 [matrix addColumn];
1825 for (i = 0; i<rows/2; i++)
1826 {
1827 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1828 atRow: i column: 1];
1829 [matrix removeRow: (rows+1)/2];
1830 }
1831 }
1832
1833 [matrix sizeToFit];
1834 {
1835 NSSize csize = [matrix cellSize];
1836 if (csize.width < MINCELLWIDTH)
1837 {
1838 csize.width = MINCELLWIDTH;
1839 [matrix setCellSize: csize];
1840 [matrix sizeToCells];
1841 }
1842 }
1843
1844 [title sizeToFit];
1845 [command sizeToFit];
1846
1847 t = [matrix frame];
1848 r = [title frame];
1849 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1850 {
1851 t.origin.x = r.origin.x;
1852 t.size.width = r.size.width;
1853 }
1854 r = [command frame];
1855 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1856 {
1857 t.origin.x = r.origin.x;
1858 t.size.width = r.size.width;
1859 }
1860
1861 r = [self frame];
1862 s = [(NSView *)[self contentView] frame];
1863 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1864 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1865 [self setFrame: r display: NO];
1866 }
1867
1868 return self;
1869}
1870
1871
1872- (void)dealloc
1873{
1874 { [super dealloc]; return; };
1875}
1876
1877
1878- (Lisp_Object)runDialogAt: (NSPoint)p
1879{
edfda783
AR
1880 int ret;
1881
3175b12a 1882 popupSession = [NSApp beginModalSessionForWindow: self];
c96169a0 1883 while (popup_activated_flag
3175b12a
AR
1884 && (ret = [NSApp runModalSession: popupSession])
1885 == NSRunContinuesResponse)
edfda783 1886 {
3175b12a
AR
1887 /* Run this for timers.el, indep of atimers; might not return.
1888 TODO: use return value to avoid calling every iteration. */
1889 timer_check (1);
1890 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
edfda783 1891 }
3175b12a 1892 [NSApp endModalSession: popupSession];
edfda783 1893
3175b12a 1894 { /* FIXME: BIG UGLY HACK!!! */
facfbbbd
SM
1895 Lisp_Object tmp;
1896 *(EMACS_INT*)(&tmp) = ret;
1897 return tmp;
1898 }
edfda783
AR
1899}
1900
1901@end
1902
1903
edfda783
AR
1904/* ==========================================================================
1905
1906 Lisp definitions
1907
1908 ========================================================================== */
1909
1910DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
eb6f7ed0 1911 doc: /* Cause the NS menu to be re-calculated. */)
edfda783
AR
1912 ()
1913{
1914 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1915 return Qnil;
1916}
1917
1918
1919DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
eb6f7ed0
CY
1920 doc: /* Pop up a deck-of-cards menu and return user's selection.
1921POSITION is a position specification. This is either a mouse button event
1922or a list ((XOFFSET YOFFSET) WINDOW)
1923where XOFFSET and YOFFSET are positions in pixels from the top left
1924corner of WINDOW. (WINDOW may be a window or a frame object.)
1925This controls the position of the top left of the menu as a whole.
1926If POSITION is t, it means to use the current mouse position.
1927
1928MENU is a specifier for a menu. For the simplest case, MENU is a keymap.
1929The menu items come from key bindings that have a menu string as well as
1930a definition; actually, the \"definition\" in such a key binding looks like
1931\(STRING . REAL-DEFINITION). To give the menu a title, put a string into
1932the keymap as a top-level element.
1933
1934If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.
1935Otherwise, REAL-DEFINITION should be a valid key binding definition.
1936
1937You can also use a list of keymaps as MENU.
1938 Then each keymap makes a separate pane.
1939
1940When MENU is a keymap or a list of keymaps, the return value is the
1941list of events corresponding to the user's choice. Note that
1942`x-popup-menu' does not actually execute the command bound to that
1943sequence of events.
1944
1945Alternatively, you can specify a menu of multiple panes
1946 with a list of the form (TITLE PANE1 PANE2...),
1947where each pane is a list of form (TITLE ITEM1 ITEM2...).
1948Each ITEM is normally a cons cell (STRING . VALUE);
1949but a string can appear as an item--that makes a nonselectable line
1950in the menu.
1951With this form of menu, the return value is VALUE from the chosen item. */)
edfda783
AR
1952 (position, menu)
1953 Lisp_Object position, menu;
1954{
1955 return ns_popup_menu (position, menu);
1956}
1957
1958
1959DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1960 doc: /* Pop up a dialog box and return user's selection.
1961POSITION specifies which frame to use.
1962This is normally a mouse button event or a window or frame.
1963If POSITION is t, it means to use the frame the mouse is on.
1964The dialog box appears in the middle of the specified frame.
1965
1966CONTENTS specifies the alternatives to display in the dialog box.
1967It is a list of the form (DIALOG ITEM1 ITEM2...).
1968Each ITEM is a cons cell (STRING . VALUE).
1969The return value is VALUE from the chosen item.
1970
1971An ITEM may also be just a string--that makes a nonselectable item.
1972An ITEM may also be nil--that means to put all preceding items
1973on the left of the dialog box and all following items on the right.
1974\(By default, approximately half appear on each side.)
1975
1976If HEADER is non-nil, the frame title for the box is "Information",
1977otherwise it is "Question".
1978
1979If the user gets rid of the dialog box without making a valid choice,
1980for instance using the window manager, then this produces a quit and
1981`x-popup-dialog' does not return. */)
1982 (position, contents, header)
1983 Lisp_Object position, contents, header;
1984{
1985 return ns_popup_dialog (position, contents, header);
1986}
1987
07b87a10
AR
1988DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1989 doc: /* Return t if a menu or popup dialog is active. */)
1990 ()
1991{
1992 return popup_activated () ? Qt : Qnil;
1993}
edfda783
AR
1994
1995/* ==========================================================================
1996
1997 Lisp interface declaration
1998
1999 ========================================================================== */
2000
2001void
2002syms_of_nsmenu ()
2003{
2004 defsubr (&Sx_popup_menu);
2005 defsubr (&Sx_popup_dialog);
2006 defsubr (&Sns_reset_menu);
07b87a10 2007 defsubr (&Smenu_or_popup_active_p);
edfda783
AR
2008 staticpro (&menu_items);
2009 menu_items = Qnil;
2010
2011 Qdebug_on_next_call = intern ("debug-on-next-call");
2012 staticpro (&Qdebug_on_next_call);
2013}
0ae1e5e5 2014
2f8e74bb 2015// arch-tag: 75773656-52e5-4c44-a398-47bd87b32619