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