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