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