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