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