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