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