* window.h (struct window): Replace hchild, vchild and buffer slots
[bpt/emacs.git] / src / nsmenu.m
CommitLineData
edfda783 1/* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
ab422c4d 2 Copyright (C) 2007-2013 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:
18e27ea8 118 1) ! deep_p, submenu = nil: Fresh switch onto a frame -- either set up
edfda783 119 just top-level menu strings (OS X), or goto case (2) (GNUstep).
18e27ea8
PE
120 2) deep_p, submenu = nil: Recompute all submenus.
121 3) deep_p, submenu = non-nil: Update contents of a single submenu.
edfda783 122 -------------------------------------------------------------------------- */
edfda783 123void
18e27ea8 124ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu)
edfda783
AR
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];
9fa1de30 131 bool owfi;
edfda783
AR
132 Lisp_Object items;
133 widget_value *wv, *first_wv, *prev_wv = 0;
134 int i;
135
cbe0b5bf 136#if NSMENUPROFILE
edfda783
AR
137 struct timeb tb;
138 long t;
139#endif
140
141 NSTRACE (set_frame_menubar);
142
143 if (f != SELECTED_FRAME ())
144 return;
145 XSETFRAME (Vmenu_updating_frame, f);
146/*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
147
4d7e6e51 148 block_input ();
edfda783
AR
149 pool = [[NSAutoreleasePool alloc] init];
150
151 /* Menu may have been created automatically; if so, discard it. */
152 if ([menu isKindOfClass: [EmacsMenu class]] == NO)
153 {
154 [menu release];
155 menu = nil;
156 }
157
158 if (menu == nil)
159 {
3988c7d6 160 menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
edfda783
AR
161 needsSet = YES;
162 }
163 else
164 { /* close up anything on there */
165 id attMenu = [menu attachedMenu];
166 if (attMenu != nil)
167 [attMenu close];
168 }
169
cbe0b5bf 170#if NSMENUPROFILE
edfda783
AR
171 ftime (&tb);
172 t = -(1000*tb.time+tb.millitm);
173#endif
174
edfda783
AR
175#ifdef NS_IMPL_GNUSTEP
176 deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
177#endif
178
179 if (deep_p)
180 {
181 /* Fully parse one or more of the submenus. */
182 int n = 0;
183 int *submenu_start, *submenu_end;
7cded46f
PE
184 bool *submenu_top_level_items;
185 int *submenu_n_panes;
edfda783
AR
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 */
e74aeda8 194 buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->contents;
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];
4d7e6e51 274 unblock_input ();
edfda783
AR
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];
4d7e6e51 336 unblock_input ();
edfda783
AR
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 406 [pool release];
4d7e6e51 407 unblock_input ();
edfda783
AR
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 437 [pool release];
4d7e6e51 438 unblock_input ();
edfda783
AR
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];
4d7e6e51 501 unblock_input ();
edfda783
AR
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
18e27ea8 510set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
edfda783
AR
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 */
0caaedb1 591 if (trackingMenu == 0)
edfda783
AR
592 return;
593/*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
594 ns_update_menubar (frame, 1, self);
595}
596
597
598- (BOOL)performKeyEquivalent: (NSEvent *)theEvent
599{
600 if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
601 && FRAME_NS_VIEW (SELECTED_FRAME ()))
602 [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
603 return YES;
604}
605
606
c7cef62d
AR
607/* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
608 into an accelerator string. We are only able to display a single character
609 for an accelerator, together with an optional modifier combination. (Under
610 Carbon more control was possible, but in Cocoa multi-char strings passed to
611 NSMenuItem get ignored. For now we try to display a super-single letter
612 combo, and return the others as strings to be appended to the item title.
613 (This is signaled by setting keyEquivModMask to 0 for now.) */
9c5bd55a 614-(NSString *)parseKeyEquiv: (const char *)key
edfda783 615{
9c5bd55a 616 const char *tpos = key;
c7cef62d
AR
617 keyEquivModMask = NSCommandKeyMask;
618
edfda783
AR
619 if (!key || !strlen (key))
620 return @"";
96fb4434 621
edfda783
AR
622 while (*tpos == ' ' || *tpos == '(')
623 tpos++;
0bae4e09
AR
624 if ((*tpos == 's') && (*(tpos+1) == '-'))
625 {
626 return [NSString stringWithFormat: @"%c", tpos[2]];
627 }
628 keyEquivModMask = 0; /* signal */
629 return [NSString stringWithUTF8String: tpos];
edfda783
AR
630}
631
07b87a10 632
15034960 633- (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
edfda783 634{
15034960 635 NSMenuItem *item;
edfda783
AR
636 widget_value *wv = (widget_value *)wvptr;
637
4039c786 638 if (menu_separator_name_p (wv->name))
edfda783
AR
639 {
640 item = [NSMenuItem separatorItem];
641 [self addItem: item];
642 }
643 else
644 {
645 NSString *title, *keyEq;
646 title = [NSString stringWithUTF8String: wv->name];
647 if (title == nil)
648 title = @"< ? >"; /* (get out in the open so we know about it) */
649
650 keyEq = [self parseKeyEquiv: wv->key];
84ee8aba
AR
651#ifdef NS_IMPL_COCOA
652 /* OS X just ignores modifier strings longer than one character */
c7cef62d
AR
653 if (keyEquivModMask == 0)
654 title = [title stringByAppendingFormat: @" (%@)", keyEq];
84ee8aba 655#endif
edfda783
AR
656
657 item = [self addItemWithTitle: (NSString *)title
658 action: @selector (menuDown:)
659 keyEquivalent: keyEq];
c7cef62d 660 [item setKeyEquivalentModifierMask: keyEquivModMask];
edfda783
AR
661
662 [item setEnabled: wv->enabled];
663
664 /* Draw radio buttons and tickboxes */
665 if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
666 wv->button_type == BUTTON_TYPE_RADIO))
667 [item setState: NSOnState];
668 else
669 [item setState: NSOffState];
670
f3f08c28 671 [item setTag: (NSInteger)wv->call_data];
edfda783
AR
672 }
673
674 return item;
675}
676
677
678/* convenience */
4ff670a8 679-(void)clear
edfda783
AR
680{
681 int n;
96fb4434 682
edfda783
AR
683 for (n = [self numberOfItems]-1; n >= 0; n--)
684 {
685 NSMenuItem *item = [self itemAtIndex: n];
686 NSString *title = [item title];
4ff670a8 687 if (([title length] == 0 /* OSX 10.5 */
3988c7d6 688 || [ns_app_name isEqualToString: title] /* from 10.6 on */
4ff670a8 689 || [@"Apple" isEqualToString: title]) /* older */
edfda783
AR
690 && ![item isSeparatorItem])
691 continue;
692 [self removeItemAtIndex: n];
693 }
694}
695
696
697- (void)fillWithWidgetValue: (void *)wvptr
698{
699 widget_value *wv = (widget_value *)wvptr;
700
701 /* clear existing contents */
702 [self setMenuChangedMessagesEnabled: NO];
703 [self clear];
704
705 /* add new contents */
706 for (; wv != NULL; wv = wv->next)
707 {
15034960 708 NSMenuItem *item = [self addItemWithWidgetValue: wv];
edfda783
AR
709
710 if (wv->contents)
711 {
c96169a0 712 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
edfda783
AR
713
714 [self setSubmenu: submenu forItem: item];
715 [submenu fillWithWidgetValue: wv->contents];
716 [submenu release];
717 [item setAction: nil];
718 }
719 }
720
721 [self setMenuChangedMessagesEnabled: YES];
722#ifdef NS_IMPL_GNUSTEP
723 if ([[self window] isVisible])
724 [self sizeToFit];
4393663b 725#endif
edfda783
AR
726}
727
728
729/* adds an empty submenu and returns it */
9c5bd55a 730- (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
edfda783
AR
731{
732 NSString *titleStr = [NSString stringWithUTF8String: title];
15034960
AR
733 NSMenuItem *item = [self addItemWithTitle: titleStr
734 action: nil /*@selector (menuDown:) */
735 keyEquivalent: @""];
edfda783
AR
736 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
737 [self setSubmenu: submenu forItem: item];
738 [submenu release];
739 return submenu;
740}
741
742/* run a menu in popup mode */
743- (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
7cded46f 744 keymaps: (bool)keymaps
edfda783
AR
745{
746 EmacsView *view = FRAME_NS_VIEW (f);
3d608a86
J
747 NSEvent *e, *event;
748 long retVal;
749
edfda783
AR
750/* p = [view convertPoint:p fromView: nil]; */
751 p.y = NSHeight ([view frame]) - p.y;
3d608a86
J
752 e = [[view window] currentEvent];
753 event = [NSEvent mouseEventWithType: NSRightMouseDown
754 location: p
755 modifierFlags: 0
756 timestamp: [e timestamp]
757 windowNumber: [[view window] windowNumber]
758 context: [e context]
759 eventNumber: 0/*[e eventNumber] */
760 clickCount: 1
761 pressure: 0];
edfda783
AR
762
763 context_menu_value = -1;
764 [NSMenu popUpContextMenu: self withEvent: event forView: view];
765 retVal = context_menu_value;
766 context_menu_value = 0;
facfbbbd
SM
767 return retVal > 0
768 ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
769 : Qnil;
edfda783
AR
770}
771
772@end /* EmacsMenu */
773
774
775
776/* ==========================================================================
777
778 Context Menu: implementing functions
779
780 ========================================================================== */
781
ef7417fd 782Lisp_Object
7cded46f 783ns_menu_show (FRAME_PTR f, int x, int y, bool for_click, bool keymaps,
42ca4633 784 Lisp_Object title, const char **error)
edfda783
AR
785{
786 EmacsMenu *pmenu;
edfda783 787 NSPoint p;
0dc8cf50 788 Lisp_Object tem;
d311d28c 789 ptrdiff_t specpdl_count = SPECPDL_INDEX ();
edfda783
AR
790 widget_value *wv, *first_wv = 0;
791
ef7417fd 792 p.x = x; p.y = y;
edfda783
AR
793
794 /* now parse stage 2 as in ns_update_menubar */
795 wv = xmalloc_widget_value ();
796 wv->name = "contextmenu";
797 wv->value = 0;
798 wv->enabled = 1;
799 wv->button_type = BUTTON_TYPE_NONE;
800 wv->help = Qnil;
801 first_wv = wv;
802
edfda783 803#if 0
df2142db 804 /* FIXME: a couple of one-line differences prevent reuse */
7cded46f 805 wv = digest_single_submenu (0, menu_items_used, 0);
edfda783
AR
806#else
807 {
808 widget_value *save_wv = 0, *prev_wv = 0;
809 widget_value **submenu_stack
38182d90 810 = alloca (menu_items_used * sizeof *submenu_stack);
edfda783 811/* Lisp_Object *subprefix_stack
38182d90 812 = alloca (menu_items_used * sizeof *subprefix_stack); */
edfda783
AR
813 int submenu_depth = 0;
814 int first_pane = 1;
815 int i;
816
817 /* Loop over all panes and items, filling in the tree. */
818 i = 0;
819 while (i < menu_items_used)
820 {
96fb4434 821 if (EQ (AREF (menu_items, i), Qnil))
edfda783
AR
822 {
823 submenu_stack[submenu_depth++] = save_wv;
824 save_wv = prev_wv;
825 prev_wv = 0;
826 first_pane = 1;
827 i++;
828 }
96fb4434 829 else if (EQ (AREF (menu_items, i), Qlambda))
edfda783
AR
830 {
831 prev_wv = save_wv;
832 save_wv = submenu_stack[--submenu_depth];
833 first_pane = 0;
834 i++;
835 }
96fb4434 836 else if (EQ (AREF (menu_items, i), Qt)
edfda783
AR
837 && submenu_depth != 0)
838 i += MENU_ITEMS_PANE_LENGTH;
839 /* Ignore a nil in the item list.
840 It's meaningful only for dialog boxes. */
96fb4434 841 else if (EQ (AREF (menu_items, i), Qquote))
edfda783 842 i += 1;
96fb4434 843 else if (EQ (AREF (menu_items, i), Qt))
edfda783
AR
844 {
845 /* Create a new pane. */
846 Lisp_Object pane_name, prefix;
9c5bd55a 847 const char *pane_string;
edfda783
AR
848
849 pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
850 prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
851
852#ifndef HAVE_MULTILINGUAL_MENU
853 if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
854 {
855 pane_name = ENCODE_MENU_STRING (pane_name);
856 ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
857 }
858#endif
859 pane_string = (NILP (pane_name)
51b59d79 860 ? "" : SSDATA (pane_name));
edfda783
AR
861 /* If there is just one top-level pane, put all its items directly
862 under the top-level menu. */
863 if (menu_items_n_panes == 1)
864 pane_string = "";
865
866 /* If the pane has a meaningful name,
867 make the pane a top-level menu item
868 with its items as a submenu beneath it. */
869 if (!keymaps && strcmp (pane_string, ""))
870 {
871 wv = xmalloc_widget_value ();
872 if (save_wv)
873 save_wv->next = wv;
874 else
875 first_wv->contents = wv;
876 wv->name = pane_string;
877 if (keymaps && !NILP (prefix))
878 wv->name++;
879 wv->value = 0;
880 wv->enabled = 1;
881 wv->button_type = BUTTON_TYPE_NONE;
882 wv->help = Qnil;
883 save_wv = wv;
884 prev_wv = 0;
885 }
886 else if (first_pane)
887 {
888 save_wv = wv;
889 prev_wv = 0;
890 }
891 first_pane = 0;
892 i += MENU_ITEMS_PANE_LENGTH;
893 }
894 else
895 {
896 /* Create a new item within current pane. */
897 Lisp_Object item_name, enable, descrip, def, type, selected, help;
898 item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
899 enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
900 descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
901 def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
902 type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
903 selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
904 help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
905
906#ifndef HAVE_MULTILINGUAL_MENU
907 if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
908 {
909 item_name = ENCODE_MENU_STRING (item_name);
910 ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
911 }
912
913 if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
914 {
915 descrip = ENCODE_MENU_STRING (descrip);
916 ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
917 }
918#endif /* not HAVE_MULTILINGUAL_MENU */
919
920 wv = xmalloc_widget_value ();
921 if (prev_wv)
922 prev_wv->next = wv;
923 else
924 save_wv->contents = wv;
51b59d79 925 wv->name = SSDATA (item_name);
edfda783 926 if (!NILP (descrip))
51b59d79 927 wv->key = SSDATA (descrip);
edfda783
AR
928 wv->value = 0;
929 /* If this item has a null value,
930 make the call_data null so that it won't display a box
931 when the mouse is on it. */
4939150c 932 wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
edfda783
AR
933 wv->enabled = !NILP (enable);
934
935 if (NILP (type))
936 wv->button_type = BUTTON_TYPE_NONE;
937 else if (EQ (type, QCtoggle))
938 wv->button_type = BUTTON_TYPE_TOGGLE;
939 else if (EQ (type, QCradio))
940 wv->button_type = BUTTON_TYPE_RADIO;
941 else
1088b922 942 emacs_abort ();
edfda783
AR
943
944 wv->selected = !NILP (selected);
945
946 if (! STRINGP (help))
947 help = Qnil;
948
949 wv->help = help;
950
951 prev_wv = wv;
952
953 i += MENU_ITEMS_ITEM_LENGTH;
954 }
955 }
956 }
957#endif
958
959 if (!NILP (title))
960 {
961 widget_value *wv_title = xmalloc_widget_value ();
962 widget_value *wv_sep = xmalloc_widget_value ();
963
964 /* Maybe replace this separator with a bitmap or owner-draw item
965 so that it looks better. Having two separators looks odd. */
966 wv_sep->name = "--";
967 wv_sep->next = first_wv->contents;
968 wv_sep->help = Qnil;
969
970#ifndef HAVE_MULTILINGUAL_MENU
971 if (STRING_MULTIBYTE (title))
972 title = ENCODE_MENU_STRING (title);
973#endif
974
51b59d79 975 wv_title->name = SSDATA (title);
15034960 976 wv_title->enabled = NO;
edfda783
AR
977 wv_title->button_type = BUTTON_TYPE_NONE;
978 wv_title->help = Qnil;
979 wv_title->next = wv_sep;
980 first_wv->contents = wv_title;
981 }
982
983 pmenu = [[EmacsMenu alloc] initWithTitle:
0dc8cf50 984 [NSString stringWithUTF8String: SSDATA (title)]];
edfda783
AR
985 [pmenu fillWithWidgetValue: first_wv->contents];
986 free_menubar_widget_value_tree (first_wv);
ef7417fd 987 unbind_to (specpdl_count, Qnil);
edfda783 988
07b87a10 989 popup_activated_flag = 1;
edfda783 990 tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
07b87a10 991 popup_activated_flag = 0;
edfda783
AR
992 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
993
edfda783
AR
994 return tem;
995}
996
997
edfda783
AR
998/* ==========================================================================
999
1000 Toolbar: externally-called functions
1001
1002 ========================================================================== */
1003
1004void
1005free_frame_tool_bar (FRAME_PTR f)
1006/* --------------------------------------------------------------------------
1007 Under NS we just hide the toolbar until it might be needed again.
1008 -------------------------------------------------------------------------- */
1009{
4d7e6e51 1010 block_input ();
edfda783 1011 [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
581a8100 1012 FRAME_TOOLBAR_HEIGHT (f) = 0;
4d7e6e51 1013 unblock_input ();
edfda783
AR
1014}
1015
1016void
1017update_frame_tool_bar (FRAME_PTR f)
1018/* --------------------------------------------------------------------------
1019 Update toolbar contents
1020 -------------------------------------------------------------------------- */
1021{
1022 int i;
581a8100
J
1023 EmacsView *view = FRAME_NS_VIEW (f);
1024 NSWindow *window = [view window];
1025 EmacsToolbar *toolbar = [view toolbar];
edfda783 1026
4d7e6e51 1027 block_input ();
edfda783
AR
1028 [toolbar clearActive];
1029
1030 /* update EmacsToolbar as in GtkUtils, build items list */
1031 for (i = 0; i < f->n_tool_bar_items; ++i)
1032 {
e69b0960 1033#define TOOLPROP(IDX) AREF (f->tool_bar_items, \
edfda783
AR
1034 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1035
1036 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
edfda783 1037 int idx;
13464394 1038 ptrdiff_t img_id;
edfda783
AR
1039 struct image *img;
1040 Lisp_Object image;
1041 Lisp_Object helpObj;
9c5bd55a 1042 const char *helpText;
edfda783
AR
1043
1044 /* If image is a vector, choose the image according to the
1045 button state. */
1046 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1047 if (VECTORP (image))
1048 {
1049 /* NS toolbar auto-computes disabled and selected images */
1050 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
ef884f23 1051 eassert (ASIZE (image) >= idx);
edfda783
AR
1052 image = AREF (image, idx);
1053 }
1054 else
1055 {
1056 idx = -1;
1057 }
dd723bbd
JD
1058 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1059 if (NILP (helpObj))
1060 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
51b59d79 1061 helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
dd723bbd 1062
edfda783
AR
1063 /* Ignore invalid image specifications. */
1064 if (!valid_image_p (image))
1065 {
dd723bbd 1066 /* Don't log anything, GNUS makes invalid images all the time. */
edfda783
AR
1067 continue;
1068 }
1069
1070 img_id = lookup_image (f, image);
1071 img = IMAGE_FROM_ID (f, img_id);
1072 prepare_image_for_display (f, img);
1073
1074 if (img->load_failed_p || img->pixmap == nil)
1075 {
1076 NSLog (@"Could not prepare toolbar image for display.");
1077 continue;
1078 }
1079
edfda783
AR
1080 [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1081 enabled: enabled_p];
1082#undef TOOLPROP
1083 }
1084
1085 if (![toolbar isVisible])
1086 [toolbar setVisible: YES];
1087
1088 if ([toolbar changed])
1089 {
1090 /* inform app that toolbar has changed */
1091 NSDictionary *dict = [toolbar configurationDictionary];
1092 NSMutableDictionary *newDict = [dict mutableCopy];
1093 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
79e721e0 1094 id key;
edfda783
AR
1095 while ((key = [keys nextObject]) != nil)
1096 {
1097 NSObject *val = [dict objectForKey: key];
1098 if ([val isKindOfClass: [NSArray class]])
1099 {
1100 [newDict setObject:
1101 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1102 forKey: key];
1103 break;
1104 }
1105 }
1106 [toolbar setConfigurationFromDictionary: newDict];
1107 [newDict release];
1108 }
1109
581a8100
J
1110 FRAME_TOOLBAR_HEIGHT (f) =
1111 NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1112 - FRAME_NS_TITLEBAR_HEIGHT (f);
6871e574
JD
1113 if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1114 FRAME_TOOLBAR_HEIGHT (f) = 0;
4d7e6e51 1115 unblock_input ();
edfda783
AR
1116}
1117
1118
1119/* ==========================================================================
1120
1121 Toolbar: class implementation
1122
1123 ========================================================================== */
1124
1125@implementation EmacsToolbar
1126
1127- initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1128{
1129 self = [super initWithIdentifier: identifier];
1130 emacsView = view;
1131 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1132 [self setSizeMode: NSToolbarSizeModeSmall];
1133 [self setDelegate: self];
1134 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1135 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1136 prevEnablement = enablement = 0L;
1137 return self;
1138}
1139
1140- (void)dealloc
1141{
1142 [prevIdentifiers release];
1143 [activeIdentifiers release];
1144 [identifierToItem release];
1145 [super dealloc];
1146}
1147
1148- (void) clearActive
1149{
1150 [prevIdentifiers release];
1151 prevIdentifiers = [activeIdentifiers copy];
1152 [activeIdentifiers removeAllObjects];
1153 prevEnablement = enablement;
1154 enablement = 0L;
1155}
1156
1157- (BOOL) changed
1158{
1159 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1160 enablement == prevEnablement ? NO : YES;
1161}
1162
1163- (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
9c5bd55a 1164 helpText: (const char *)help enabled: (BOOL)enabled
edfda783
AR
1165{
1166 /* 1) come up w/identifier */
facfbbbd
SM
1167 NSString *identifier
1168 = [NSString stringWithFormat: @"%u", [img hash]];
edfda783
AR
1169
1170 /* 2) create / reuse item */
1171 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1172 if (item == nil)
1173 {
1174 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1175 autorelease];
1176 [item setImage: img];
f3f08c28 1177 [item setToolTip: [NSString stringWithUTF8String: help]];
edfda783
AR
1178 [item setTarget: emacsView];
1179 [item setAction: @selector (toolbarClicked:)];
1180 }
1181
1182 [item setTag: idx];
1183 [item setEnabled: enabled];
1184
1185 /* 3) update state */
1186 [identifierToItem setObject: item forKey: identifier];
1187 [activeIdentifiers addObject: identifier];
1188 enablement = (enablement << 1) | (enabled == YES);
1189}
1190
1191/* This overrides super's implementation, which automatically sets
1192 all items to enabled state (for some reason). */
1193- (void)validateVisibleItems { }
1194
1195
1196/* delegate methods */
1197
1198- (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1199 itemForItemIdentifier: (NSString *)itemIdentifier
1200 willBeInsertedIntoToolbar: (BOOL)flag
1201{
1202 /* look up NSToolbarItem by identifier and return... */
1203 return [identifierToItem objectForKey: itemIdentifier];
1204}
1205
1206- (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1207{
1208 /* return entire set.. */
1209 return activeIdentifiers;
1210}
1211
1212/* for configuration palette (not yet supported) */
1213- (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1214{
1215 /* return entire set... */
1216 return [identifierToItem allKeys];
1217}
1218
1219/* optional and unneeded */
1220/* - toolbarWillAddItem: (NSNotification *)notification { } */
1221/* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1222/* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1223
1224@end /* EmacsToolbar */
1225
1226
1227
1228/* ==========================================================================
1229
1230 Tooltip: class implementation
1231
1232 ========================================================================== */
1233
1234/* Needed because NeXTstep does not provide enough control over tooltip
1235 display. */
1236@implementation EmacsTooltip
1237
1238- init
1239{
1240 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1241 blue: 0.792 alpha: 0.95];
1242 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1243 NSFont *sfont = [font screenFont];
1244 int height = [sfont ascender] - [sfont descender];
1245/*[font boundingRectForFont].size.height; */
1246 NSRect r = NSMakeRect (0, 0, 100, height+6);
1247
1248 textField = [[NSTextField alloc] initWithFrame: r];
1249 [textField setFont: font];
1250 [textField setBackgroundColor: col];
1251
1252 [textField setEditable: NO];
1253 [textField setSelectable: NO];
ffe57a7a
AA
1254 [textField setBordered: NO];
1255 [textField setBezeled: NO];
edfda783
AR
1256 [textField setDrawsBackground: YES];
1257
1258 win = [[NSWindow alloc]
1259 initWithContentRect: [textField frame]
1260 styleMask: 0
1261 backing: NSBackingStoreBuffered
1262 defer: YES];
ffe57a7a 1263 [win setHasShadow: YES];
edfda783
AR
1264 [win setReleasedWhenClosed: NO];
1265 [win setDelegate: self];
1266 [[win contentView] addSubview: textField];
1267/* [win setBackgroundColor: col]; */
1268 [win setOpaque: NO];
1269
1270 return self;
1271}
1272
1273- (void) dealloc
1274{
1275 [win close];
1276 [win release];
1277 [textField release];
1278 [super dealloc];
1279}
1280
1281- (void) setText: (char *)text
1282{
1283 NSString *str = [NSString stringWithUTF8String: text];
ffe57a7a
AA
1284 NSRect r = [textField frame];
1285 NSSize tooltipDims;
1286
edfda783 1287 [textField setStringValue: str];
ffe57a7a
AA
1288 tooltipDims = [[textField cell] cellSize];
1289
1290 r.size.width = tooltipDims.width;
1291 r.size.height = tooltipDims.height;
1292 [textField setFrame: r];
edfda783
AR
1293}
1294
1295- (void) showAtX: (int)x Y: (int)y for: (int)seconds
1296{
1297 NSRect wr = [win frame];
1298
1299 wr.origin = NSMakePoint (x, y);
1300 wr.size = [textField frame].size;
1301
1302 [win setFrame: wr display: YES];
1303 [win orderFront: self];
1304 [win display];
1305 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1306 selector: @selector (hide)
1307 userInfo: nil repeats: NO];
1308 [timer retain];
1309}
1310
1311- (void) hide
1312{
1313 [win close];
1314 if (timer != nil)
1315 {
1316 if ([timer isValid])
1317 [timer invalidate];
1318 [timer release];
1319 timer = nil;
1320 }
1321}
1322
1323- (BOOL) isActive
1324{
1325 return timer != nil;
1326}
1327
1328- (NSRect) frame
1329{
1330 return [textField frame];
1331}
1332
1333@end /* EmacsTooltip */
1334
1335
1336
1337/* ==========================================================================
1338
1339 Popup Dialog: implementing functions
1340
1341 ========================================================================== */
1342
9d7fa573
JD
1343struct Popdown_data
1344{
1345 NSAutoreleasePool *pool;
1346 EmacsDialogPanel *dialog;
1347};
c96169a0
AR
1348
1349static Lisp_Object
1350pop_down_menu (Lisp_Object arg)
1351{
2b30549c 1352 struct Popdown_data *unwind_data = XSAVE_POINTER (arg, 0);
9d7fa573 1353
4d7e6e51 1354 block_input ();
ba301db3
AR
1355 if (popup_activated_flag)
1356 {
9d7fa573 1357 EmacsDialogPanel *panel = unwind_data->dialog;
ba301db3 1358 popup_activated_flag = 0;
9d7fa573
JD
1359 [panel close];
1360 [unwind_data->pool release];
ba301db3 1361 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
ba301db3 1362 }
9d7fa573
JD
1363
1364 xfree (unwind_data);
4d7e6e51 1365 unblock_input ();
9d7fa573 1366
c96169a0
AR
1367 return Qnil;
1368}
1369
1370
edfda783
AR
1371Lisp_Object
1372ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1373{
1374 id dialog;
d6f0886c 1375 Lisp_Object window, tem, title;
edfda783
AR
1376 struct frame *f;
1377 NSPoint p;
1378 BOOL isQ;
9d7fa573 1379 NSAutoreleasePool *pool;
edfda783
AR
1380
1381 NSTRACE (x-popup-dialog);
96fb4434 1382
edfda783
AR
1383 check_ns ();
1384
1385 isQ = NILP (header);
1386
8612b71a
AR
1387 if (EQ (position, Qt)
1388 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1389 || EQ (XCAR (position), Qtool_bar))))
edfda783
AR
1390 {
1391 window = selected_window;
1392 }
1393 else if (CONSP (position))
1394 {
1395 Lisp_Object tem;
1396 tem = Fcar (position);
1397 if (XTYPE (tem) == Lisp_Cons)
1398 window = Fcar (Fcdr (position));
1399 else
1400 {
1401 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1402 window = Fcar (tem); /* POSN_WINDOW (tem) */
1403 }
1404 }
8612b71a 1405 else if (WINDOWP (position) || FRAMEP (position))
edfda783
AR
1406 {
1407 window = position;
1408 }
1409 else
8612b71a
AR
1410 window = Qnil;
1411
edfda783
AR
1412 if (FRAMEP (window))
1413 f = XFRAME (window);
8612b71a 1414 else if (WINDOWP (window))
edfda783
AR
1415 {
1416 CHECK_LIVE_WINDOW (window);
1417 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1418 }
8612b71a
AR
1419 else
1420 CHECK_WINDOW (window);
1421
edfda783
AR
1422 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1423 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
3175b12a 1424
d6f0886c
JD
1425 title = Fcar (contents);
1426 CHECK_STRING (title);
1427
1428 if (NILP (Fcar (Fcdr (contents))))
1429 /* No buttons specified, add an "Ok" button so users can pop down
1430 the dialog. */
1431 contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1432
4d7e6e51 1433 block_input ();
9d7fa573 1434 pool = [[NSAutoreleasePool alloc] init];
edfda783
AR
1435 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1436 isQuestion: isQ];
9d7fa573 1437
c96169a0 1438 {
d311d28c 1439 ptrdiff_t specpdl_count = SPECPDL_INDEX ();
9d7fa573
JD
1440 struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1441
1442 unwind_data->pool = pool;
1443 unwind_data->dialog = dialog;
4939150c 1444
468afbac 1445 record_unwind_protect (pop_down_menu, make_save_pointer (unwind_data));
c96169a0
AR
1446 popup_activated_flag = 1;
1447 tem = [dialog runDialogAt: p];
ba301db3 1448 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
c96169a0 1449 }
9d7fa573 1450
4d7e6e51 1451 unblock_input ();
c96169a0 1452
edfda783
AR
1453 return tem;
1454}
1455
1456
1457/* ==========================================================================
1458
1459 Popup Dialog: class implementation
1460
1461 ========================================================================== */
1462
1463@interface FlippedView : NSView
1464{
1465}
1466@end
1467
1468@implementation FlippedView
1469- (BOOL)isFlipped
1470{
1471 return YES;
1472}
1473@end
1474
1475@implementation EmacsDialogPanel
1476
1477#define SPACER 8.0
1478#define ICONSIZE 64.0
1479#define TEXTHEIGHT 20.0
1480#define MINCELLWIDTH 90.0
1481
f3f08c28 1482- initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
edfda783
AR
1483 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1484{
1485 NSSize spacing = {SPACER, SPACER};
1486 NSRect area;
facfbbbd 1487 id cell;
9d7fa573
JD
1488 NSImageView *imgView;
1489 FlippedView *contentView;
1490 NSImage *img;
1491
0f19feff 1492 dialog_return = Qundefined;
7f8941d8 1493 button_values = NULL;
9d7fa573
JD
1494 area.origin.x = 3*SPACER;
1495 area.origin.y = 2*SPACER;
1496 area.size.width = ICONSIZE;
1497 area.size.height= ICONSIZE;
1498 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1499 [img setScalesWhenResized: YES];
1500 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1501 imgView = [[NSImageView alloc] initWithFrame: area];
1502 [imgView setImage: img];
1503 [imgView setEditable: NO];
1504 [img autorelease];
1505 [imgView autorelease];
edfda783
AR
1506
1507 aStyle = NSTitledWindowMask;
1508 flag = YES;
1509 rows = 0;
1510 cols = 1;
1511 [super initWithContentRect: contentRect styleMask: aStyle
1512 backing: backingType defer: flag];
1513 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
9d7fa573
JD
1514 [contentView autorelease];
1515
edfda783
AR
1516 [self setContentView: contentView];
1517
1518 [[self contentView] setAutoresizesSubviews: YES];
1519
1520 [[self contentView] addSubview: imgView];
1521 [self setTitle: @""];
1522
1523 area.origin.x += ICONSIZE+2*SPACER;
1524/* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1525 area.size.width = 400;
1526 area.size.height= TEXTHEIGHT;
1527 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1528 [[self contentView] addSubview: command];
3988c7d6 1529 [command setStringValue: ns_app_name];
edfda783
AR
1530 [command setDrawsBackground: NO];
1531 [command setBezeled: NO];
1532 [command setSelectable: NO];
1533 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1534
1535/* area.origin.x = ICONSIZE+2*SPACER;
1536 area.origin.y = TEXTHEIGHT + 2*SPACER;
1537 area.size.width = 400;
1538 area.size.height= 2;
1539 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1540 [[self contentView] addSubview: tem];
1541 [tem setTitlePosition: NSNoTitle];
1542 [tem setAutoresizingMask: NSViewWidthSizable];*/
1543
1544/* area.origin.x = ICONSIZE+2*SPACER; */
1545 area.origin.y += TEXTHEIGHT+SPACER;
1546 area.size.width = 400;
1547 area.size.height= TEXTHEIGHT;
1548 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1549 [[self contentView] addSubview: title];
1550 [title setDrawsBackground: NO];
1551 [title setBezeled: NO];
1552 [title setSelectable: NO];
1553 [title setFont: [NSFont systemFontOfSize: 11.0]];
1554
1555 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1556 [cell setBordered: NO];
1557 [cell setEnabled: NO];
1558 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1559 [cell setBezelStyle: NSRoundedBezelStyle];
1560
96fb4434
PE
1561 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1562 mode: NSHighlightModeMatrix
1563 prototype: cell
1564 numberOfRows: 0
edfda783 1565 numberOfColumns: 1];
edfda783
AR
1566 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1567 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1568 [matrix setIntercellSpacing: spacing];
9d7fa573 1569 [matrix autorelease];
edfda783 1570
9d7fa573 1571 [[self contentView] addSubview: matrix];
edfda783
AR
1572 [self setOneShot: YES];
1573 [self setReleasedWhenClosed: YES];
1574 [self setHidesOnDeactivate: YES];
7f8941d8
JD
1575 [self setStyleMask:
1576 NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask];
1577
edfda783
AR
1578 return self;
1579}
1580
1581
1582- (BOOL)windowShouldClose: (id)sender
1583{
7f8941d8 1584 window_closed = YES;
0f19feff 1585 [NSApp stop:self];
edfda783
AR
1586 return NO;
1587}
1588
7f8941d8
JD
1589- (void)dealloc
1590{
1591 xfree (button_values);
1592 [super dealloc];
1593}
edfda783 1594
7f8941d8 1595- (void)process_dialog: (Lisp_Object) list
edfda783 1596{
7f8941d8 1597 Lisp_Object item, lst = list;
edfda783 1598 int row = 0;
7f8941d8
JD
1599 int buttons = 0, btnnr = 0;
1600
1601 for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1602 {
1603 item = XCAR (list);
1604 if (XTYPE (item) == Lisp_Cons)
1605 ++buttons;
1606 }
1607
1608 if (buttons > 0)
1609 button_values = (Lisp_Object *) xmalloc (buttons * sizeof (*button_values));
edfda783
AR
1610
1611 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1612 {
1613 item = XCAR (list);
1614 if (XTYPE (item) == Lisp_String)
1615 {
7f8941d8 1616 [self addString: SSDATA (item) row: row++];
edfda783
AR
1617 }
1618 else if (XTYPE (item) == Lisp_Cons)
1619 {
7f8941d8
JD
1620 button_values[btnnr] = XCDR (item);
1621 [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1622 ++btnnr;
edfda783
AR
1623 }
1624 else if (NILP (item))
1625 {
7f8941d8 1626 [self addSplit];
edfda783
AR
1627 row = 0;
1628 }
1629 }
1630}
1631
1632
7f8941d8 1633- (void)addButton: (char *)str value: (int)tag row: (int)row
edfda783
AR
1634{
1635 id cell;
96fb4434 1636
edfda783
AR
1637 if (row >= rows)
1638 {
1639 [matrix addRow];
1640 rows++;
1641 }
1642 cell = [matrix cellAtRow: row column: cols-1];
1643 [cell setTarget: self];
1644 [cell setAction: @selector (clicked: )];
1645 [cell setTitle: [NSString stringWithUTF8String: str]];
7f8941d8 1646 [cell setTag: tag];
edfda783
AR
1647 [cell setBordered: YES];
1648 [cell setEnabled: YES];
edfda783
AR
1649}
1650
1651
7f8941d8 1652- (void)addString: (char *)str row: (int)row
edfda783
AR
1653{
1654 id cell;
96fb4434 1655
edfda783
AR
1656 if (row >= rows)
1657 {
1658 [matrix addRow];
1659 rows++;
1660 }
1661 cell = [matrix cellAtRow: row column: cols-1];
1662 [cell setTitle: [NSString stringWithUTF8String: str]];
1663 [cell setBordered: YES];
1664 [cell setEnabled: NO];
edfda783
AR
1665}
1666
1667
7f8941d8 1668- (void)addSplit
edfda783
AR
1669{
1670 [matrix addColumn];
1671 cols++;
edfda783
AR
1672}
1673
1674
7f8941d8 1675- (void)clicked: sender
edfda783
AR
1676{
1677 NSArray *sellist = nil;
facfbbbd 1678 EMACS_INT seltag;
edfda783
AR
1679
1680 sellist = [sender selectedCells];
7f8941d8
JD
1681 if ([sellist count] < 1)
1682 return;
edfda783 1683
facfbbbd 1684 seltag = [[sellist objectAtIndex: 0] tag];
7f8941d8
JD
1685 dialog_return = button_values[seltag];
1686 [NSApp stop:self];
edfda783
AR
1687}
1688
1689
1690- initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1691{
1692 Lisp_Object head;
1693 [super init];
1694
1695 if (XTYPE (contents) == Lisp_Cons)
1696 {
1697 head = Fcar (contents);
7f8941d8 1698 [self process_dialog: Fcdr (contents)];
edfda783
AR
1699 }
1700 else
1701 head = contents;
1702
1703 if (XTYPE (head) == Lisp_String)
1704 [title setStringValue:
0dc8cf50 1705 [NSString stringWithUTF8String: SSDATA (head)]];
edfda783
AR
1706 else if (isQ == YES)
1707 [title setStringValue: @"Question"];
1708 else
1709 [title setStringValue: @"Information"];
1710
1711 {
1712 int i;
1713 NSRect r, s, t;
1714
1715 if (cols == 1 && rows > 1) /* Never told where to split */
1716 {
1717 [matrix addColumn];
7f8941d8 1718 for (i = 0; i < rows/2; i++)
edfda783
AR
1719 {
1720 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1721 atRow: i column: 1];
1722 [matrix removeRow: (rows+1)/2];
1723 }
1724 }
1725
1726 [matrix sizeToFit];
1727 {
1728 NSSize csize = [matrix cellSize];
1729 if (csize.width < MINCELLWIDTH)
1730 {
1731 csize.width = MINCELLWIDTH;
1732 [matrix setCellSize: csize];
1733 [matrix sizeToCells];
1734 }
1735 }
1736
1737 [title sizeToFit];
1738 [command sizeToFit];
1739
1740 t = [matrix frame];
1741 r = [title frame];
1742 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1743 {
1744 t.origin.x = r.origin.x;
1745 t.size.width = r.size.width;
1746 }
1747 r = [command frame];
1748 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1749 {
1750 t.origin.x = r.origin.x;
1751 t.size.width = r.size.width;
1752 }
1753
1754 r = [self frame];
1755 s = [(NSView *)[self contentView] frame];
1756 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1757 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1758 [self setFrame: r display: NO];
1759 }
1760
1761 return self;
1762}
1763
1764
18e27ea8 1765
ddee6515
JD
1766- (void)timeout_handler: (NSTimer *)timedEntry
1767{
0f19feff
JD
1768 NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1769 location: NSMakePoint (0, 0)
1770 modifierFlags: 0
1771 timestamp: 0
1772 windowNumber: [[NSApp mainWindow] windowNumber]
1773 context: [NSApp context]
1774 subtype: 0
1775 data1: 0
1776 data2: 0];
1777
7f8941d8 1778 timer_fired = YES;
0f19feff
JD
1779 /* We use sto because stopModal/abortModal out of the main loop does not
1780 seem to work in 10.6. But as we use stop we must send a real event so
1781 the stop is seen and acted upon. */
1782 [NSApp stop:self];
1783 [NSApp postEvent: nxev atStart: NO];
ddee6515
JD
1784}
1785
edfda783
AR
1786- (Lisp_Object)runDialogAt: (NSPoint)p
1787{
0f19feff 1788 Lisp_Object ret = Qundefined;
edfda783 1789
ddee6515 1790 while (popup_activated_flag)
edfda783 1791 {
ddee6515
JD
1792 NSTimer *tmo = nil;
1793 EMACS_TIME next_time = timer_check ();
1794
1795 if (EMACS_TIME_VALID_P (next_time))
1796 {
1797 double time = EMACS_TIME_TO_DOUBLE (next_time);
1798 tmo = [NSTimer timerWithTimeInterval: time
1799 target: self
1800 selector: @selector (timeout_handler:)
1801 userInfo: 0
1802 repeats: NO];
1803 [[NSRunLoop currentRunLoop] addTimer: tmo
1804 forMode: NSModalPanelRunLoopMode];
1805 }
7f8941d8 1806 timer_fired = NO;
0f19feff 1807 dialog_return = Qundefined;
7f8941d8 1808 [NSApp runModalForWindow: self];
0f19feff 1809 ret = dialog_return;
ddee6515
JD
1810 if (! timer_fired)
1811 {
1812 if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1813 break;
1814 }
edfda783 1815 }
edfda783 1816
7f8941d8
JD
1817 if (EQ (ret, Qundefined) && window_closed)
1818 /* Make close button pressed equivalent to C-g. */
1819 Fsignal (Qquit, Qnil);
1820
1821 return ret;
edfda783
AR
1822}
1823
1824@end
1825
1826
edfda783
AR
1827/* ==========================================================================
1828
1829 Lisp definitions
1830
1831 ========================================================================== */
1832
1833DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
eb6f7ed0 1834 doc: /* Cause the NS menu to be re-calculated. */)
5842a27b 1835 (void)
edfda783
AR
1836{
1837 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1838 return Qnil;
1839}
1840
1841
edfda783
AR
1842DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1843 doc: /* Pop up a dialog box and return user's selection.
1844POSITION specifies which frame to use.
1845This is normally a mouse button event or a window or frame.
1846If POSITION is t, it means to use the frame the mouse is on.
1847The dialog box appears in the middle of the specified frame.
1848
1849CONTENTS specifies the alternatives to display in the dialog box.
1850It is a list of the form (DIALOG ITEM1 ITEM2...).
1851Each ITEM is a cons cell (STRING . VALUE).
1852The return value is VALUE from the chosen item.
1853
1854An ITEM may also be just a string--that makes a nonselectable item.
1855An ITEM may also be nil--that means to put all preceding items
1856on the left of the dialog box and all following items on the right.
1857\(By default, approximately half appear on each side.)
1858
1859If HEADER is non-nil, the frame title for the box is "Information",
1860otherwise it is "Question".
1861
1862If the user gets rid of the dialog box without making a valid choice,
1863for instance using the window manager, then this produces a quit and
1864`x-popup-dialog' does not return. */)
5842a27b 1865 (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
edfda783
AR
1866{
1867 return ns_popup_dialog (position, contents, header);
1868}
1869
07b87a10
AR
1870DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1871 doc: /* Return t if a menu or popup dialog is active. */)
5842a27b 1872 (void)
07b87a10
AR
1873{
1874 return popup_activated () ? Qt : Qnil;
1875}
edfda783
AR
1876
1877/* ==========================================================================
1878
1879 Lisp interface declaration
1880
1881 ========================================================================== */
1882
1883void
3d608a86 1884syms_of_nsmenu (void)
edfda783 1885{
5fecd5fc
JD
1886#ifndef NS_IMPL_COCOA
1887 /* Don't know how to keep track of this in Next/Open/Gnustep. Always
1888 update menus there. */
1889 trackingMenu = 1;
1890#endif
edfda783
AR
1891 defsubr (&Sx_popup_dialog);
1892 defsubr (&Sns_reset_menu);
07b87a10 1893 defsubr (&Smenu_or_popup_active_p);
edfda783 1894
088dcc3e 1895 Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
edfda783
AR
1896 staticpro (&Qdebug_on_next_call);
1897}