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