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