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