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