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