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