Port better to POSIX hosts lacking _setjmp.
[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
28 #include "lisp.h"
29 #include "window.h"
30 #include "character.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
76 /* Nonzero means we are tracking and updating menus. */
77 static int trackingMenu;
78
79
80 /* NOTE: toolbar implementation is at end,
81 following complete menu implementation. */
82
83
84 /* ==========================================================================
85
86 Menu: Externally-called functions
87
88 ========================================================================== */
89
90
91 /* FIXME: not currently used, but should normalize with other terms. */
92 void
93 x_activate_menubar (struct frame *f)
94 {
95 fprintf (stderr, "XXX: Received x_activate_menubar event.\n");
96 }
97
98
99 /* Supposed to discard menubar and free storage. Since we share the
100 menubar among frames and update its context for the focused window,
101 there is nothing to do here. */
102 void
103 free_frame_menubar (struct frame *f)
104 {
105 return;
106 }
107
108
109 int
110 popup_activated (void)
111 {
112 return popup_activated_flag;
113 }
114
115
116 /* --------------------------------------------------------------------------
117 Update menubar. Three cases:
118 1) deep_p = 0, submenu = nil: Fresh switch onto a frame -- either set up
119 just top-level menu strings (OS X), or goto case (2) (GNUstep).
120 2) deep_p = 1, submenu = nil: Recompute all submenus.
121 3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
122 -------------------------------------------------------------------------- */
123 void
124 ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
125 {
126 NSAutoreleasePool *pool;
127 id menu = [NSApp mainMenu];
128 static EmacsMenu *last_submenu = nil;
129 BOOL needsSet = NO;
130 const char *submenuTitle = [[submenu title] UTF8String];
131 extern int waiting_for_input;
132 int owfi;
133 Lisp_Object items;
134 widget_value *wv, *first_wv, *prev_wv = 0;
135 int i;
136
137 #if NSMENUPROFILE
138 struct timeb tb;
139 long t;
140 #endif
141
142 NSTRACE (set_frame_menubar);
143
144 if (f != SELECTED_FRAME ())
145 return;
146 XSETFRAME (Vmenu_updating_frame, f);
147 /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */
148
149 BLOCK_INPUT;
150 pool = [[NSAutoreleasePool alloc] init];
151
152 /* Menu may have been created automatically; if so, discard it. */
153 if ([menu isKindOfClass: [EmacsMenu class]] == NO)
154 {
155 [menu release];
156 menu = nil;
157 }
158
159 if (menu == nil)
160 {
161 menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
162 needsSet = YES;
163 }
164 else
165 { /* close up anything on there */
166 id attMenu = [menu attachedMenu];
167 if (attMenu != nil)
168 [attMenu close];
169 }
170
171 #if NSMENUPROFILE
172 ftime (&tb);
173 t = -(1000*tb.time+tb.millitm);
174 #endif
175
176 #ifdef NS_IMPL_GNUSTEP
177 deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
178 #endif
179
180 if (deep_p)
181 {
182 /* Fully parse one or more of the submenus. */
183 int n = 0;
184 int *submenu_start, *submenu_end;
185 int *submenu_top_level_items, *submenu_n_panes;
186 struct buffer *prev = current_buffer;
187 Lisp_Object buffer;
188 ptrdiff_t specpdl_count = SPECPDL_INDEX ();
189 int previous_menu_items_used = f->menu_bar_items_used;
190 Lisp_Object *previous_items
191 = alloca (previous_menu_items_used * sizeof *previous_items);
192
193 /* lisp preliminaries */
194 buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer;
195 specbind (Qinhibit_quit, Qt);
196 specbind (Qdebug_on_next_call, Qnil);
197 record_unwind_save_match_data ();
198 if (NILP (Voverriding_local_map_menu_flag))
199 {
200 specbind (Qoverriding_terminal_local_map, Qnil);
201 specbind (Qoverriding_local_map, Qnil);
202 }
203 set_buffer_internal_1 (XBUFFER (buffer));
204
205 /* TODO: for some reason this is not needed in other terms,
206 but some menu updates call Info-extract-pointer which causes
207 abort-on-error if waiting-for-input. Needs further investigation. */
208 owfi = waiting_for_input;
209 waiting_for_input = 0;
210
211 /* lucid hook and possible reset */
212 safe_run_hooks (Qactivate_menubar_hook);
213 if (! NILP (Vlucid_menu_bar_dirty_flag))
214 call0 (Qrecompute_lucid_menubar);
215 safe_run_hooks (Qmenu_bar_update_hook);
216 fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
217
218 /* Now ready to go */
219 items = FRAME_MENU_BAR_ITEMS (f);
220
221 /* Save the frame's previous menu bar contents data */
222 if (previous_menu_items_used)
223 memcpy (previous_items, aref_addr (f->menu_bar_vector, 0),
224 previous_menu_items_used * sizeof (Lisp_Object));
225
226 /* parse stage 1: extract from lisp */
227 save_menu_items ();
228
229 menu_items = f->menu_bar_vector;
230 menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
231 submenu_start = alloca (ASIZE (items) * sizeof *submenu_start);
232 submenu_end = alloca (ASIZE (items) * sizeof *submenu_end);
233 submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes);
234 submenu_top_level_items = alloca (ASIZE (items)
235 * sizeof *submenu_top_level_items);
236 init_menu_items ();
237 for (i = 0; i < ASIZE (items); i += 4)
238 {
239 Lisp_Object key, string, maps;
240
241 key = AREF (items, i);
242 string = AREF (items, i + 1);
243 maps = AREF (items, i + 2);
244 if (NILP (string))
245 break;
246
247 /* FIXME: we'd like to only parse the needed submenu, but this
248 was causing crashes in the _common parsing code.. need to make
249 sure proper initialization done.. */
250 /* if (submenu && strcmp (submenuTitle, SSDATA (string)))
251 continue; */
252
253 submenu_start[i] = menu_items_used;
254
255 menu_items_n_panes = 0;
256 submenu_top_level_items[i] = parse_single_submenu (key, string, maps);
257 submenu_n_panes[i] = menu_items_n_panes;
258 submenu_end[i] = menu_items_used;
259 n++;
260 }
261
262 finish_menu_items ();
263 waiting_for_input = owfi;
264
265
266 if (submenu && n == 0)
267 {
268 /* should have found a menu for this one but didn't */
269 fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n",
270 submenuTitle);
271 discard_menu_items ();
272 unbind_to (specpdl_count, Qnil);
273 [pool release];
274 UNBLOCK_INPUT;
275 return;
276 }
277
278 /* parse stage 2: insert into lucid 'widget_value' structures
279 [comments in other terms say not to evaluate lisp code here] */
280 wv = xmalloc_widget_value ();
281 wv->name = "menubar";
282 wv->value = 0;
283 wv->enabled = 1;
284 wv->button_type = BUTTON_TYPE_NONE;
285 wv->help = Qnil;
286 first_wv = wv;
287
288 for (i = 0; i < 4*n; i += 4)
289 {
290 menu_items_n_panes = submenu_n_panes[i];
291 wv = digest_single_submenu (submenu_start[i], submenu_end[i],
292 submenu_top_level_items[i]);
293 if (prev_wv)
294 prev_wv->next = wv;
295 else
296 first_wv->contents = wv;
297 /* Don't set wv->name here; GC during the loop might relocate it. */
298 wv->enabled = 1;
299 wv->button_type = BUTTON_TYPE_NONE;
300 prev_wv = wv;
301 }
302
303 set_buffer_internal_1 (prev);
304
305 /* Compare the new menu items with previous, and leave off if no change */
306 /* FIXME: following other terms here, but seems like this should be
307 done before parse stage 2 above, since its results aren't used */
308 if (previous_menu_items_used
309 && (!submenu || (submenu && submenu == last_submenu))
310 && menu_items_used == previous_menu_items_used)
311 {
312 for (i = 0; i < previous_menu_items_used; i++)
313 /* FIXME: this ALWAYS fails on Buffers menu items.. something
314 about their strings causes them to change every time, so we
315 double-check failures */
316 if (!EQ (previous_items[i], AREF (menu_items, i)))
317 if (!(STRINGP (previous_items[i])
318 && STRINGP (AREF (menu_items, i))
319 && !strcmp (SSDATA (previous_items[i]),
320 SSDATA (AREF (menu_items, i)))))
321 break;
322 if (i == previous_menu_items_used)
323 {
324 /* No change.. */
325
326 #if NSMENUPROFILE
327 ftime (&tb);
328 t += 1000*tb.time+tb.millitm;
329 fprintf (stderr, "NO CHANGE! CUTTING OUT after %ld msec.\n", t);
330 #endif
331
332 free_menubar_widget_value_tree (first_wv);
333 discard_menu_items ();
334 unbind_to (specpdl_count, Qnil);
335 [pool release];
336 UNBLOCK_INPUT;
337 return;
338 }
339 }
340 /* The menu items are different, so store them in the frame */
341 /* FIXME: this is not correct for single-submenu case */
342 fset_menu_bar_vector (f, menu_items);
343 f->menu_bar_items_used = menu_items_used;
344
345 /* Calls restore_menu_items, etc., as they were outside */
346 unbind_to (specpdl_count, Qnil);
347
348 /* Parse stage 2a: now GC cannot happen during the lifetime of the
349 widget_value, so it's safe to store data from a Lisp_String */
350 wv = first_wv->contents;
351 for (i = 0; i < ASIZE (items); i += 4)
352 {
353 Lisp_Object string;
354 string = AREF (items, i + 1);
355 if (NILP (string))
356 break;
357 /* if (submenu && strcmp (submenuTitle, SSDATA (string)))
358 continue; */
359
360 wv->name = SSDATA (string);
361 update_submenu_strings (wv->contents);
362 wv = wv->next;
363 }
364
365 /* Now, update the NS menu; if we have a submenu, use that, otherwise
366 create a new menu for each sub and fill it. */
367 if (submenu)
368 {
369 for (wv = first_wv->contents; wv; wv = wv->next)
370 {
371 if (!strcmp (submenuTitle, wv->name))
372 {
373 [submenu fillWithWidgetValue: wv->contents];
374 last_submenu = submenu;
375 break;
376 }
377 }
378 }
379 else
380 {
381 [menu fillWithWidgetValue: first_wv->contents];
382 }
383
384 }
385 else
386 {
387 static int n_previous_strings = 0;
388 static char previous_strings[100][10];
389 static struct frame *last_f = NULL;
390 int n;
391 Lisp_Object string;
392
393 wv = xmalloc_widget_value ();
394 wv->name = "menubar";
395 wv->value = 0;
396 wv->enabled = 1;
397 wv->button_type = BUTTON_TYPE_NONE;
398 wv->help = Qnil;
399 first_wv = wv;
400
401 /* Make widget-value tree w/ just the top level menu bar strings */
402 items = FRAME_MENU_BAR_ITEMS (f);
403 if (NILP (items))
404 {
405 free_menubar_widget_value_tree (first_wv);
406 [pool release];
407 UNBLOCK_INPUT;
408 return;
409 }
410
411
412 /* check if no change.. this mechanism is a bit rough, but ready */
413 n = ASIZE (items) / 4;
414 if (f == last_f && n_previous_strings == n)
415 {
416 for (i = 0; i<n; i++)
417 {
418 string = AREF (items, 4*i+1);
419
420 if (EQ (string, make_number (0))) // FIXME: Why??? --Stef
421 continue;
422 if (NILP (string))
423 {
424 if (previous_strings[i][0])
425 break;
426 else
427 continue;
428 }
429 else if (memcmp (previous_strings[i], SDATA (string),
430 min (10, SBYTES (string) + 1)))
431 break;
432 }
433
434 if (i == n)
435 {
436 free_menubar_widget_value_tree (first_wv);
437 [pool release];
438 UNBLOCK_INPUT;
439 return;
440 }
441 }
442
443 [menu clear];
444 for (i = 0; i < ASIZE (items); i += 4)
445 {
446 string = AREF (items, i + 1);
447 if (NILP (string))
448 break;
449
450 if (n < 100)
451 memcpy (previous_strings[i/4], SDATA (string),
452 min (10, SBYTES (string) + 1));
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 *) (intptr_t) (-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 tem;
797 ptrdiff_t 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 = alloca (menu_items_used * sizeof *submenu_stack);
819 /* Lisp_Object *subprefix_stack
820 = alloca (menu_items_used * sizeof *subprefix_stack); */
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 = !NILP (def) ? aref_addr (menu_items, i) : 0;
941 wv->enabled = !NILP (enable);
942
943 if (NILP (type))
944 wv->button_type = BUTTON_TYPE_NONE;
945 else if (EQ (type, QCtoggle))
946 wv->button_type = BUTTON_TYPE_TOGGLE;
947 else if (EQ (type, QCradio))
948 wv->button_type = BUTTON_TYPE_RADIO;
949 else
950 emacs_abort ();
951
952 wv->selected = !NILP (selected);
953
954 if (! STRINGP (help))
955 help = Qnil;
956
957 wv->help = help;
958
959 prev_wv = wv;
960
961 i += MENU_ITEMS_ITEM_LENGTH;
962 }
963 }
964 }
965 #endif
966
967 if (!NILP (title))
968 {
969 widget_value *wv_title = xmalloc_widget_value ();
970 widget_value *wv_sep = xmalloc_widget_value ();
971
972 /* Maybe replace this separator with a bitmap or owner-draw item
973 so that it looks better. Having two separators looks odd. */
974 wv_sep->name = "--";
975 wv_sep->next = first_wv->contents;
976 wv_sep->help = Qnil;
977
978 #ifndef HAVE_MULTILINGUAL_MENU
979 if (STRING_MULTIBYTE (title))
980 title = ENCODE_MENU_STRING (title);
981 #endif
982
983 wv_title->name = SSDATA (title);
984 wv_title->enabled = NO;
985 wv_title->button_type = BUTTON_TYPE_NONE;
986 wv_title->help = Qnil;
987 wv_title->next = wv_sep;
988 first_wv->contents = wv_title;
989 }
990
991 pmenu = [[EmacsMenu alloc] initWithTitle:
992 [NSString stringWithUTF8String: SSDATA (title)]];
993 [pmenu fillWithWidgetValue: first_wv->contents];
994 free_menubar_widget_value_tree (first_wv);
995 unbind_to (specpdl_count, Qnil);
996
997 popup_activated_flag = 1;
998 tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
999 popup_activated_flag = 0;
1000 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1001
1002 return tem;
1003 }
1004
1005
1006 /* ==========================================================================
1007
1008 Toolbar: externally-called functions
1009
1010 ========================================================================== */
1011
1012 void
1013 free_frame_tool_bar (FRAME_PTR f)
1014 /* --------------------------------------------------------------------------
1015 Under NS we just hide the toolbar until it might be needed again.
1016 -------------------------------------------------------------------------- */
1017 {
1018 BLOCK_INPUT;
1019 [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1020 FRAME_TOOLBAR_HEIGHT (f) = 0;
1021 UNBLOCK_INPUT;
1022 }
1023
1024 void
1025 update_frame_tool_bar (FRAME_PTR f)
1026 /* --------------------------------------------------------------------------
1027 Update toolbar contents
1028 -------------------------------------------------------------------------- */
1029 {
1030 int i;
1031 EmacsView *view = FRAME_NS_VIEW (f);
1032 NSWindow *window = [view window];
1033 EmacsToolbar *toolbar = [view toolbar];
1034
1035 BLOCK_INPUT;
1036 [toolbar clearActive];
1037
1038 /* update EmacsToolbar as in GtkUtils, build items list */
1039 for (i = 0; i < f->n_tool_bar_items; ++i)
1040 {
1041 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1042 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1043
1044 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1045 int idx;
1046 ptrdiff_t img_id;
1047 struct image *img;
1048 Lisp_Object image;
1049 Lisp_Object helpObj;
1050 const char *helpText;
1051
1052 /* If image is a vector, choose the image according to the
1053 button state. */
1054 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1055 if (VECTORP (image))
1056 {
1057 /* NS toolbar auto-computes disabled and selected images */
1058 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1059 eassert (ASIZE (image) >= idx);
1060 image = AREF (image, idx);
1061 }
1062 else
1063 {
1064 idx = -1;
1065 }
1066 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1067 if (NILP (helpObj))
1068 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1069 helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1070
1071 /* Ignore invalid image specifications. */
1072 if (!valid_image_p (image))
1073 {
1074 /* Don't log anything, GNUS makes invalid images all the time. */
1075 continue;
1076 }
1077
1078 img_id = lookup_image (f, image);
1079 img = IMAGE_FROM_ID (f, img_id);
1080 prepare_image_for_display (f, img);
1081
1082 if (img->load_failed_p || img->pixmap == nil)
1083 {
1084 NSLog (@"Could not prepare toolbar image for display.");
1085 continue;
1086 }
1087
1088 [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1089 enabled: enabled_p];
1090 #undef TOOLPROP
1091 }
1092
1093 if (![toolbar isVisible])
1094 [toolbar setVisible: YES];
1095
1096 if ([toolbar changed])
1097 {
1098 /* inform app that toolbar has changed */
1099 NSDictionary *dict = [toolbar configurationDictionary];
1100 NSMutableDictionary *newDict = [dict mutableCopy];
1101 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1102 id key;
1103 while ((key = [keys nextObject]) != nil)
1104 {
1105 NSObject *val = [dict objectForKey: key];
1106 if ([val isKindOfClass: [NSArray class]])
1107 {
1108 [newDict setObject:
1109 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1110 forKey: key];
1111 break;
1112 }
1113 }
1114 [toolbar setConfigurationFromDictionary: newDict];
1115 [newDict release];
1116 }
1117
1118 FRAME_TOOLBAR_HEIGHT (f) =
1119 NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1120 - FRAME_NS_TITLEBAR_HEIGHT (f);
1121 UNBLOCK_INPUT;
1122 }
1123
1124
1125 /* ==========================================================================
1126
1127 Toolbar: class implementation
1128
1129 ========================================================================== */
1130
1131 @implementation EmacsToolbar
1132
1133 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1134 {
1135 self = [super initWithIdentifier: identifier];
1136 emacsView = view;
1137 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1138 [self setSizeMode: NSToolbarSizeModeSmall];
1139 [self setDelegate: self];
1140 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1141 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1142 prevEnablement = enablement = 0L;
1143 return self;
1144 }
1145
1146 - (void)dealloc
1147 {
1148 [prevIdentifiers release];
1149 [activeIdentifiers release];
1150 [identifierToItem release];
1151 [super dealloc];
1152 }
1153
1154 - (void) clearActive
1155 {
1156 [prevIdentifiers release];
1157 prevIdentifiers = [activeIdentifiers copy];
1158 [activeIdentifiers removeAllObjects];
1159 prevEnablement = enablement;
1160 enablement = 0L;
1161 }
1162
1163 - (BOOL) changed
1164 {
1165 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1166 enablement == prevEnablement ? NO : YES;
1167 }
1168
1169 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1170 helpText: (const char *)help enabled: (BOOL)enabled
1171 {
1172 /* 1) come up w/identifier */
1173 NSString *identifier
1174 = [NSString stringWithFormat: @"%u", [img hash]];
1175
1176 /* 2) create / reuse item */
1177 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1178 if (item == nil)
1179 {
1180 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1181 autorelease];
1182 [item setImage: img];
1183 [item setToolTip: [NSString stringWithUTF8String: help]];
1184 [item setTarget: emacsView];
1185 [item setAction: @selector (toolbarClicked:)];
1186 }
1187
1188 [item setTag: idx];
1189 [item setEnabled: enabled];
1190
1191 /* 3) update state */
1192 [identifierToItem setObject: item forKey: identifier];
1193 [activeIdentifiers addObject: identifier];
1194 enablement = (enablement << 1) | (enabled == YES);
1195 }
1196
1197 /* This overrides super's implementation, which automatically sets
1198 all items to enabled state (for some reason). */
1199 - (void)validateVisibleItems { }
1200
1201
1202 /* delegate methods */
1203
1204 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1205 itemForItemIdentifier: (NSString *)itemIdentifier
1206 willBeInsertedIntoToolbar: (BOOL)flag
1207 {
1208 /* look up NSToolbarItem by identifier and return... */
1209 return [identifierToItem objectForKey: itemIdentifier];
1210 }
1211
1212 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1213 {
1214 /* return entire set.. */
1215 return activeIdentifiers;
1216 }
1217
1218 /* for configuration palette (not yet supported) */
1219 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1220 {
1221 /* return entire set... */
1222 return [identifierToItem allKeys];
1223 }
1224
1225 /* optional and unneeded */
1226 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1227 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1228 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1229
1230 @end /* EmacsToolbar */
1231
1232
1233
1234 /* ==========================================================================
1235
1236 Tooltip: class implementation
1237
1238 ========================================================================== */
1239
1240 /* Needed because NeXTstep does not provide enough control over tooltip
1241 display. */
1242 @implementation EmacsTooltip
1243
1244 - init
1245 {
1246 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1247 blue: 0.792 alpha: 0.95];
1248 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1249 NSFont *sfont = [font screenFont];
1250 int height = [sfont ascender] - [sfont descender];
1251 /*[font boundingRectForFont].size.height; */
1252 NSRect r = NSMakeRect (0, 0, 100, height+6);
1253
1254 textField = [[NSTextField alloc] initWithFrame: r];
1255 [textField setFont: font];
1256 [textField setBackgroundColor: col];
1257
1258 [textField setEditable: NO];
1259 [textField setSelectable: NO];
1260 [textField setBordered: NO];
1261 [textField setBezeled: NO];
1262 [textField setDrawsBackground: YES];
1263
1264 win = [[NSWindow alloc]
1265 initWithContentRect: [textField frame]
1266 styleMask: 0
1267 backing: NSBackingStoreBuffered
1268 defer: YES];
1269 [win setHasShadow: YES];
1270 [win setReleasedWhenClosed: NO];
1271 [win setDelegate: self];
1272 [[win contentView] addSubview: textField];
1273 /* [win setBackgroundColor: col]; */
1274 [win setOpaque: NO];
1275
1276 return self;
1277 }
1278
1279 - (void) dealloc
1280 {
1281 [win close];
1282 [win release];
1283 [textField release];
1284 [super dealloc];
1285 }
1286
1287 - (void) setText: (char *)text
1288 {
1289 NSString *str = [NSString stringWithUTF8String: text];
1290 NSRect r = [textField frame];
1291 NSSize tooltipDims;
1292
1293 [textField setStringValue: str];
1294 tooltipDims = [[textField cell] cellSize];
1295
1296 r.size.width = tooltipDims.width;
1297 r.size.height = tooltipDims.height;
1298 [textField setFrame: r];
1299 }
1300
1301 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1302 {
1303 NSRect wr = [win frame];
1304
1305 wr.origin = NSMakePoint (x, y);
1306 wr.size = [textField frame].size;
1307
1308 [win setFrame: wr display: YES];
1309 [win orderFront: self];
1310 [win display];
1311 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1312 selector: @selector (hide)
1313 userInfo: nil repeats: NO];
1314 [timer retain];
1315 }
1316
1317 - (void) hide
1318 {
1319 [win close];
1320 if (timer != nil)
1321 {
1322 if ([timer isValid])
1323 [timer invalidate];
1324 [timer release];
1325 timer = nil;
1326 }
1327 }
1328
1329 - (BOOL) isActive
1330 {
1331 return timer != nil;
1332 }
1333
1334 - (NSRect) frame
1335 {
1336 return [textField frame];
1337 }
1338
1339 @end /* EmacsTooltip */
1340
1341
1342
1343 /* ==========================================================================
1344
1345 Popup Dialog: implementing functions
1346
1347 ========================================================================== */
1348
1349 struct Popdown_data
1350 {
1351 NSAutoreleasePool *pool;
1352 EmacsDialogPanel *dialog;
1353 };
1354
1355 static Lisp_Object
1356 pop_down_menu (Lisp_Object arg)
1357 {
1358 struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1359 struct Popdown_data *unwind_data = (struct Popdown_data *) p->pointer;
1360
1361 BLOCK_INPUT;
1362 if (popup_activated_flag)
1363 {
1364 EmacsDialogPanel *panel = unwind_data->dialog;
1365 popup_activated_flag = 0;
1366 [panel close];
1367 [unwind_data->pool release];
1368 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1369 }
1370
1371 xfree (unwind_data);
1372 UNBLOCK_INPUT;
1373
1374 return Qnil;
1375 }
1376
1377
1378 Lisp_Object
1379 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1380 {
1381 id dialog;
1382 Lisp_Object window, tem, title;
1383 struct frame *f;
1384 NSPoint p;
1385 BOOL isQ;
1386 NSAutoreleasePool *pool;
1387
1388 NSTRACE (x-popup-dialog);
1389
1390 check_ns ();
1391
1392 isQ = NILP (header);
1393
1394 if (EQ (position, Qt)
1395 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1396 || EQ (XCAR (position), Qtool_bar))))
1397 {
1398 window = selected_window;
1399 }
1400 else if (CONSP (position))
1401 {
1402 Lisp_Object tem;
1403 tem = Fcar (position);
1404 if (XTYPE (tem) == Lisp_Cons)
1405 window = Fcar (Fcdr (position));
1406 else
1407 {
1408 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1409 window = Fcar (tem); /* POSN_WINDOW (tem) */
1410 }
1411 }
1412 else if (WINDOWP (position) || FRAMEP (position))
1413 {
1414 window = position;
1415 }
1416 else
1417 window = Qnil;
1418
1419 if (FRAMEP (window))
1420 f = XFRAME (window);
1421 else if (WINDOWP (window))
1422 {
1423 CHECK_LIVE_WINDOW (window);
1424 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1425 }
1426 else
1427 CHECK_WINDOW (window);
1428
1429 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1430 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1431
1432 title = Fcar (contents);
1433 CHECK_STRING (title);
1434
1435 if (NILP (Fcar (Fcdr (contents))))
1436 /* No buttons specified, add an "Ok" button so users can pop down
1437 the dialog. */
1438 contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
1439
1440 BLOCK_INPUT;
1441 pool = [[NSAutoreleasePool alloc] init];
1442 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1443 isQuestion: isQ];
1444
1445 {
1446 ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1447 struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1448
1449 unwind_data->pool = pool;
1450 unwind_data->dialog = dialog;
1451
1452 record_unwind_protect (pop_down_menu, make_save_value (unwind_data, 0));
1453 popup_activated_flag = 1;
1454 tem = [dialog runDialogAt: p];
1455 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
1456 }
1457
1458 UNBLOCK_INPUT;
1459
1460 return tem;
1461 }
1462
1463
1464 /* ==========================================================================
1465
1466 Popup Dialog: class implementation
1467
1468 ========================================================================== */
1469
1470 @interface FlippedView : NSView
1471 {
1472 }
1473 @end
1474
1475 @implementation FlippedView
1476 - (BOOL)isFlipped
1477 {
1478 return YES;
1479 }
1480 @end
1481
1482 @implementation EmacsDialogPanel
1483
1484 #define SPACER 8.0
1485 #define ICONSIZE 64.0
1486 #define TEXTHEIGHT 20.0
1487 #define MINCELLWIDTH 90.0
1488
1489 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1490 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1491 {
1492 NSSize spacing = {SPACER, SPACER};
1493 NSRect area;
1494 id cell;
1495 NSImageView *imgView;
1496 FlippedView *contentView;
1497 NSImage *img;
1498
1499 dialog_return = Qundefined;
1500 button_values = NULL;
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 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1574 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1575 [matrix setIntercellSpacing: spacing];
1576 [matrix autorelease];
1577
1578 [[self contentView] addSubview: matrix];
1579 [self setOneShot: YES];
1580 [self setReleasedWhenClosed: YES];
1581 [self setHidesOnDeactivate: YES];
1582 [self setStyleMask:
1583 NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask];
1584
1585 return self;
1586 }
1587
1588
1589 - (BOOL)windowShouldClose: (id)sender
1590 {
1591 window_closed = YES;
1592 [NSApp stop:self];
1593 return NO;
1594 }
1595
1596 - (void)dealloc
1597 {
1598 xfree (button_values);
1599 [super dealloc];
1600 }
1601
1602 - (void)process_dialog: (Lisp_Object) list
1603 {
1604 Lisp_Object item, lst = list;
1605 int row = 0;
1606 int buttons = 0, btnnr = 0;
1607
1608 for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1609 {
1610 item = XCAR (list);
1611 if (XTYPE (item) == Lisp_Cons)
1612 ++buttons;
1613 }
1614
1615 if (buttons > 0)
1616 button_values = (Lisp_Object *) xmalloc (buttons * sizeof (*button_values));
1617
1618 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1619 {
1620 item = XCAR (list);
1621 if (XTYPE (item) == Lisp_String)
1622 {
1623 [self addString: SSDATA (item) row: row++];
1624 }
1625 else if (XTYPE (item) == Lisp_Cons)
1626 {
1627 button_values[btnnr] = XCDR (item);
1628 [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1629 ++btnnr;
1630 }
1631 else if (NILP (item))
1632 {
1633 [self addSplit];
1634 row = 0;
1635 }
1636 }
1637 }
1638
1639
1640 - (void)addButton: (char *)str value: (int)tag 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 setTarget: self];
1651 [cell setAction: @selector (clicked: )];
1652 [cell setTitle: [NSString stringWithUTF8String: str]];
1653 [cell setTag: tag];
1654 [cell setBordered: YES];
1655 [cell setEnabled: YES];
1656 }
1657
1658
1659 - (void)addString: (char *)str row: (int)row
1660 {
1661 id cell;
1662
1663 if (row >= rows)
1664 {
1665 [matrix addRow];
1666 rows++;
1667 }
1668 cell = [matrix cellAtRow: row column: cols-1];
1669 [cell setTitle: [NSString stringWithUTF8String: str]];
1670 [cell setBordered: YES];
1671 [cell setEnabled: NO];
1672 }
1673
1674
1675 - (void)addSplit
1676 {
1677 [matrix addColumn];
1678 cols++;
1679 }
1680
1681
1682 - (void)clicked: sender
1683 {
1684 NSArray *sellist = nil;
1685 EMACS_INT seltag;
1686
1687 sellist = [sender selectedCells];
1688 if ([sellist count] < 1)
1689 return;
1690
1691 seltag = [[sellist objectAtIndex: 0] tag];
1692 dialog_return = button_values[seltag];
1693 [NSApp stop:self];
1694 }
1695
1696
1697 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1698 {
1699 Lisp_Object head;
1700 [super init];
1701
1702 if (XTYPE (contents) == Lisp_Cons)
1703 {
1704 head = Fcar (contents);
1705 [self process_dialog: Fcdr (contents)];
1706 }
1707 else
1708 head = contents;
1709
1710 if (XTYPE (head) == Lisp_String)
1711 [title setStringValue:
1712 [NSString stringWithUTF8String: SSDATA (head)]];
1713 else if (isQ == YES)
1714 [title setStringValue: @"Question"];
1715 else
1716 [title setStringValue: @"Information"];
1717
1718 {
1719 int i;
1720 NSRect r, s, t;
1721
1722 if (cols == 1 && rows > 1) /* Never told where to split */
1723 {
1724 [matrix addColumn];
1725 for (i = 0; i < rows/2; i++)
1726 {
1727 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1728 atRow: i column: 1];
1729 [matrix removeRow: (rows+1)/2];
1730 }
1731 }
1732
1733 [matrix sizeToFit];
1734 {
1735 NSSize csize = [matrix cellSize];
1736 if (csize.width < MINCELLWIDTH)
1737 {
1738 csize.width = MINCELLWIDTH;
1739 [matrix setCellSize: csize];
1740 [matrix sizeToCells];
1741 }
1742 }
1743
1744 [title sizeToFit];
1745 [command sizeToFit];
1746
1747 t = [matrix frame];
1748 r = [title frame];
1749 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1750 {
1751 t.origin.x = r.origin.x;
1752 t.size.width = r.size.width;
1753 }
1754 r = [command frame];
1755 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1756 {
1757 t.origin.x = r.origin.x;
1758 t.size.width = r.size.width;
1759 }
1760
1761 r = [self frame];
1762 s = [(NSView *)[self contentView] frame];
1763 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1764 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1765 [self setFrame: r display: NO];
1766 }
1767
1768 return self;
1769 }
1770
1771
1772
1773 - (void)timeout_handler: (NSTimer *)timedEntry
1774 {
1775 NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1776 location: NSMakePoint (0, 0)
1777 modifierFlags: 0
1778 timestamp: 0
1779 windowNumber: [[NSApp mainWindow] windowNumber]
1780 context: [NSApp context]
1781 subtype: 0
1782 data1: 0
1783 data2: 0];
1784
1785 timer_fired = YES;
1786 /* We use sto because stopModal/abortModal out of the main loop does not
1787 seem to work in 10.6. But as we use stop we must send a real event so
1788 the stop is seen and acted upon. */
1789 [NSApp stop:self];
1790 [NSApp postEvent: nxev atStart: NO];
1791 }
1792
1793 - (Lisp_Object)runDialogAt: (NSPoint)p
1794 {
1795 Lisp_Object ret = Qundefined;
1796
1797 while (popup_activated_flag)
1798 {
1799 NSTimer *tmo = nil;
1800 EMACS_TIME next_time = timer_check ();
1801
1802 if (EMACS_TIME_VALID_P (next_time))
1803 {
1804 double time = EMACS_TIME_TO_DOUBLE (next_time);
1805 tmo = [NSTimer timerWithTimeInterval: time
1806 target: self
1807 selector: @selector (timeout_handler:)
1808 userInfo: 0
1809 repeats: NO];
1810 [[NSRunLoop currentRunLoop] addTimer: tmo
1811 forMode: NSModalPanelRunLoopMode];
1812 }
1813 timer_fired = NO;
1814 dialog_return = Qundefined;
1815 [NSApp runModalForWindow: self];
1816 ret = dialog_return;
1817 if (! timer_fired)
1818 {
1819 if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1820 break;
1821 }
1822 }
1823
1824 if (EQ (ret, Qundefined) && window_closed)
1825 /* Make close button pressed equivalent to C-g. */
1826 Fsignal (Qquit, Qnil);
1827
1828 return ret;
1829 }
1830
1831 @end
1832
1833
1834 /* ==========================================================================
1835
1836 Lisp definitions
1837
1838 ========================================================================== */
1839
1840 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1841 doc: /* Cause the NS menu to be re-calculated. */)
1842 (void)
1843 {
1844 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1845 return Qnil;
1846 }
1847
1848
1849 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1850 doc: /* Pop up a dialog box and return user's selection.
1851 POSITION specifies which frame to use.
1852 This is normally a mouse button event or a window or frame.
1853 If POSITION is t, it means to use the frame the mouse is on.
1854 The dialog box appears in the middle of the specified frame.
1855
1856 CONTENTS specifies the alternatives to display in the dialog box.
1857 It is a list of the form (DIALOG ITEM1 ITEM2...).
1858 Each ITEM is a cons cell (STRING . VALUE).
1859 The return value is VALUE from the chosen item.
1860
1861 An ITEM may also be just a string--that makes a nonselectable item.
1862 An ITEM may also be nil--that means to put all preceding items
1863 on the left of the dialog box and all following items on the right.
1864 \(By default, approximately half appear on each side.)
1865
1866 If HEADER is non-nil, the frame title for the box is "Information",
1867 otherwise it is "Question".
1868
1869 If the user gets rid of the dialog box without making a valid choice,
1870 for instance using the window manager, then this produces a quit and
1871 `x-popup-dialog' does not return. */)
1872 (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1873 {
1874 return ns_popup_dialog (position, contents, header);
1875 }
1876
1877 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1878 doc: /* Return t if a menu or popup dialog is active. */)
1879 (void)
1880 {
1881 return popup_activated () ? Qt : Qnil;
1882 }
1883
1884 /* ==========================================================================
1885
1886 Lisp interface declaration
1887
1888 ========================================================================== */
1889
1890 void
1891 syms_of_nsmenu (void)
1892 {
1893 #ifndef NS_IMPL_COCOA
1894 /* Don't know how to keep track of this in Next/Open/Gnustep. Always
1895 update menus there. */
1896 trackingMenu = 1;
1897 #endif
1898 defsubr (&Sx_popup_dialog);
1899 defsubr (&Sns_reset_menu);
1900 defsubr (&Smenu_or_popup_active_p);
1901
1902 Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1903 staticpro (&Qdebug_on_next_call);
1904 }