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