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