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