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