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