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