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