Merge from emacs-24; up to 2013-01-02T10:15:31Z!michael.albinus@gmx.de
[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 frame: f];
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 ns_update_menubar (f, true, nil);
508 ns_check_pending_open_menu ();
509 #endif
510 }
511
512
513
514
515 /* ==========================================================================
516
517 Menu: class implementation
518
519 ========================================================================== */
520
521
522 /* Menu that can define itself from Emacs "widget_value"s and will lazily
523 update itself when user clicked. Based on Carbon/AppKit implementation
524 by Yamamoto Mitsuharu. */
525 @implementation EmacsMenu
526
527 /* override designated initializer */
528 - initWithTitle: (NSString *)title
529 {
530 frame = 0;
531 if ((self = [super initWithTitle: title]))
532 [self setAutoenablesItems: NO];
533 return self;
534 }
535
536
537 /* used for top-level */
538 - initWithTitle: (NSString *)title frame: (struct frame *)f
539 {
540 [self initWithTitle: title];
541 frame = f;
542 #ifdef NS_IMPL_COCOA
543 [self setDelegate: self];
544 #endif
545 return self;
546 }
547
548
549 - (void)setFrame: (struct frame *)f
550 {
551 frame = f;
552 }
553
554 #ifdef NS_IMPL_COCOA
555 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
556 extern NSString *NSMenuDidBeginTrackingNotification;
557 #endif
558 #endif
559
560 #ifdef NS_IMPL_COCOA
561 -(void)trackingNotification:(NSNotification *)notification
562 {
563 /* Update menu in menuNeedsUpdate only while tracking menus. */
564 trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification
565 ? 1 : 0);
566 if (! trackingMenu) ns_check_menu_open (nil);
567 }
568
569 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
570 - (void)menuWillOpen:(NSMenu *)menu
571 {
572 ++trackingMenu;
573
574 #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_6
575 // On 10.6 we get repeated calls, only the one for NSSystemDefined is "real".
576 if ([[NSApp currentEvent] type] != NSSystemDefined) return;
577 #endif
578
579 /* When dragging from one menu to another, we get willOpen followed by didClose,
580 i.e. trackingMenu == 3 in willOpen and then 2 after didClose.
581 We have updated all menus, so avoid doing it when trackingMenu == 3. */
582 if (trackingMenu == 2)
583 ns_check_menu_open (menu);
584 }
585
586 - (void)menuDidClose:(NSMenu *)menu
587 {
588 --trackingMenu;
589 }
590 #endif /* OSX >= 10.5 */
591
592 #endif /* NS_IMPL_COCOA */
593
594 /* delegate method called when a submenu is being opened: run a 'deep' call
595 to set_frame_menubar */
596 - (void)menuNeedsUpdate: (NSMenu *)menu
597 {
598 if (!FRAME_LIVE_P (frame))
599 return;
600
601 /* Cocoa/Carbon will request update on every keystroke
602 via IsMenuKeyEvent -> CheckMenusForKeyEvent. These are not needed
603 since key equivalents are handled through emacs.
604 On Leopard, even keystroke events generate SystemDefined event.
605 Third-party applications that enhance mouse / trackpad
606 interaction, or also VNC/Remote Desktop will send events
607 of type AppDefined rather than SysDefined.
608 Menus will fail to show up if they haven't been initialized.
609 AppDefined events may lack timing data.
610
611 Thus, we rely on the didBeginTrackingNotification notification
612 as above to indicate the need for updates.
613 From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the
614 key press case, NSMenuPropertyItemImage (e.g.) won't be set.
615 */
616 if (trackingMenu == 0)
617 return;
618 /*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
619 #if (! defined (NS_IMPL_COCOA) \
620 || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5)
621 /* Don't know how to do this for anything other than OSX >= 10.5
622 This is wrong, as it might run Lisp code in the event loop. */
623 ns_update_menubar (frame, true, self);
624 #endif
625 }
626
627
628 - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
629 {
630 if (SELECTED_FRAME () && FRAME_NS_P (SELECTED_FRAME ())
631 && FRAME_NS_VIEW (SELECTED_FRAME ()))
632 [FRAME_NS_VIEW (SELECTED_FRAME ()) keyDown: theEvent];
633 return YES;
634 }
635
636
637 /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
638 into an accelerator string. We are only able to display a single character
639 for an accelerator, together with an optional modifier combination. (Under
640 Carbon more control was possible, but in Cocoa multi-char strings passed to
641 NSMenuItem get ignored. For now we try to display a super-single letter
642 combo, and return the others as strings to be appended to the item title.
643 (This is signaled by setting keyEquivModMask to 0 for now.) */
644 -(NSString *)parseKeyEquiv: (const char *)key
645 {
646 const char *tpos = key;
647 keyEquivModMask = NSCommandKeyMask;
648
649 if (!key || !strlen (key))
650 return @"";
651
652 while (*tpos == ' ' || *tpos == '(')
653 tpos++;
654 if ((*tpos == 's') && (*(tpos+1) == '-'))
655 {
656 return [NSString stringWithFormat: @"%c", tpos[2]];
657 }
658 keyEquivModMask = 0; /* signal */
659 return [NSString stringWithUTF8String: tpos];
660 }
661
662
663 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
664 {
665 NSMenuItem *item;
666 widget_value *wv = (widget_value *)wvptr;
667
668 if (menu_separator_name_p (wv->name))
669 {
670 item = [NSMenuItem separatorItem];
671 [self addItem: item];
672 }
673 else
674 {
675 NSString *title, *keyEq;
676 title = [NSString stringWithUTF8String: wv->name];
677 if (title == nil)
678 title = @"< ? >"; /* (get out in the open so we know about it) */
679
680 keyEq = [self parseKeyEquiv: wv->key];
681 #ifdef NS_IMPL_COCOA
682 /* OS X just ignores modifier strings longer than one character */
683 if (keyEquivModMask == 0)
684 title = [title stringByAppendingFormat: @" (%@)", keyEq];
685 #endif
686
687 item = [self addItemWithTitle: (NSString *)title
688 action: @selector (menuDown:)
689 keyEquivalent: keyEq];
690 [item setKeyEquivalentModifierMask: keyEquivModMask];
691
692 [item setEnabled: wv->enabled];
693
694 /* Draw radio buttons and tickboxes */
695 if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
696 wv->button_type == BUTTON_TYPE_RADIO))
697 [item setState: NSOnState];
698 else
699 [item setState: NSOffState];
700
701 [item setTag: (NSInteger)wv->call_data];
702 }
703
704 return item;
705 }
706
707
708 /* convenience */
709 -(void)clear
710 {
711 int n;
712
713 for (n = [self numberOfItems]-1; n >= 0; n--)
714 {
715 NSMenuItem *item = [self itemAtIndex: n];
716 NSString *title = [item title];
717 if (([title length] == 0 /* OSX 10.5 */
718 || [ns_app_name isEqualToString: title] /* from 10.6 on */
719 || [@"Apple" isEqualToString: title]) /* older */
720 && ![item isSeparatorItem])
721 continue;
722 [self removeItemAtIndex: n];
723 }
724 }
725
726
727 - (void)fillWithWidgetValue: (void *)wvptr
728 {
729 [self fillWithWidgetValue: wvptr frame:nil];
730 }
731
732 - (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f
733 {
734 widget_value *wv = (widget_value *)wvptr;
735
736 /* clear existing contents */
737 [self setMenuChangedMessagesEnabled: NO];
738 [self clear];
739
740 /* add new contents */
741 for (; wv != NULL; wv = wv->next)
742 {
743 NSMenuItem *item = [self addItemWithWidgetValue: wv];
744
745 if (wv->contents)
746 {
747 EmacsMenu *submenu;
748
749 if (f)
750 submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f];
751 else
752 submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
753
754 [self setSubmenu: submenu forItem: item];
755 [submenu fillWithWidgetValue: wv->contents];
756 [submenu release];
757 [item setAction: (SEL)nil];
758 }
759 }
760
761 [self setMenuChangedMessagesEnabled: YES];
762 #ifdef NS_IMPL_GNUSTEP
763 if ([[self window] isVisible])
764 [self sizeToFit];
765 #endif
766 }
767
768
769 /* adds an empty submenu and returns it */
770 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
771 {
772 NSString *titleStr = [NSString stringWithUTF8String: title];
773 NSMenuItem *item = [self addItemWithTitle: titleStr
774 action: (SEL)nil /*@selector (menuDown:) */
775 keyEquivalent: @""];
776 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
777 [self setSubmenu: submenu forItem: item];
778 [submenu release];
779 return submenu;
780 }
781
782 /* run a menu in popup mode */
783 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
784 keymaps: (bool)keymaps
785 {
786 EmacsView *view = FRAME_NS_VIEW (f);
787 NSEvent *e, *event;
788 long retVal;
789
790 /* p = [view convertPoint:p fromView: nil]; */
791 p.y = NSHeight ([view frame]) - p.y;
792 e = [[view window] currentEvent];
793 event = [NSEvent mouseEventWithType: NSRightMouseDown
794 location: p
795 modifierFlags: 0
796 timestamp: [e timestamp]
797 windowNumber: [[view window] windowNumber]
798 context: [e context]
799 eventNumber: 0/*[e eventNumber] */
800 clickCount: 1
801 pressure: 0];
802
803 context_menu_value = -1;
804 [NSMenu popUpContextMenu: self withEvent: event forView: view];
805 retVal = context_menu_value;
806 context_menu_value = 0;
807 return retVal > 0
808 ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
809 : Qnil;
810 }
811
812 @end /* EmacsMenu */
813
814
815
816 /* ==========================================================================
817
818 Context Menu: implementing functions
819
820 ========================================================================== */
821
822 Lisp_Object
823 ns_menu_show (struct frame *f, int x, int y, bool for_click, bool keymaps,
824 Lisp_Object title, const char **error)
825 {
826 EmacsMenu *pmenu;
827 NSPoint p;
828 Lisp_Object tem;
829 ptrdiff_t specpdl_count = SPECPDL_INDEX ();
830 widget_value *wv, *first_wv = 0;
831
832 p.x = x; p.y = y;
833
834 /* now parse stage 2 as in ns_update_menubar */
835 wv = xmalloc_widget_value ();
836 wv->name = "contextmenu";
837 wv->value = 0;
838 wv->enabled = 1;
839 wv->button_type = BUTTON_TYPE_NONE;
840 wv->help = Qnil;
841 first_wv = wv;
842
843 #if 0
844 /* FIXME: a couple of one-line differences prevent reuse */
845 wv = digest_single_submenu (0, menu_items_used, 0);
846 #else
847 {
848 widget_value *save_wv = 0, *prev_wv = 0;
849 widget_value **submenu_stack
850 = alloca (menu_items_used * sizeof *submenu_stack);
851 /* Lisp_Object *subprefix_stack
852 = alloca (menu_items_used * sizeof *subprefix_stack); */
853 int submenu_depth = 0;
854 int first_pane = 1;
855 int i;
856
857 /* Loop over all panes and items, filling in the tree. */
858 i = 0;
859 while (i < menu_items_used)
860 {
861 if (EQ (AREF (menu_items, i), Qnil))
862 {
863 submenu_stack[submenu_depth++] = save_wv;
864 save_wv = prev_wv;
865 prev_wv = 0;
866 first_pane = 1;
867 i++;
868 }
869 else if (EQ (AREF (menu_items, i), Qlambda))
870 {
871 prev_wv = save_wv;
872 save_wv = submenu_stack[--submenu_depth];
873 first_pane = 0;
874 i++;
875 }
876 else if (EQ (AREF (menu_items, i), Qt)
877 && submenu_depth != 0)
878 i += MENU_ITEMS_PANE_LENGTH;
879 /* Ignore a nil in the item list.
880 It's meaningful only for dialog boxes. */
881 else if (EQ (AREF (menu_items, i), Qquote))
882 i += 1;
883 else if (EQ (AREF (menu_items, i), Qt))
884 {
885 /* Create a new pane. */
886 Lisp_Object pane_name, prefix;
887 const char *pane_string;
888
889 pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
890 prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
891
892 #ifndef HAVE_MULTILINGUAL_MENU
893 if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
894 {
895 pane_name = ENCODE_MENU_STRING (pane_name);
896 ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
897 }
898 #endif
899 pane_string = (NILP (pane_name)
900 ? "" : SSDATA (pane_name));
901 /* If there is just one top-level pane, put all its items directly
902 under the top-level menu. */
903 if (menu_items_n_panes == 1)
904 pane_string = "";
905
906 /* If the pane has a meaningful name,
907 make the pane a top-level menu item
908 with its items as a submenu beneath it. */
909 if (!keymaps && strcmp (pane_string, ""))
910 {
911 wv = xmalloc_widget_value ();
912 if (save_wv)
913 save_wv->next = wv;
914 else
915 first_wv->contents = wv;
916 wv->name = pane_string;
917 if (keymaps && !NILP (prefix))
918 wv->name++;
919 wv->value = 0;
920 wv->enabled = 1;
921 wv->button_type = BUTTON_TYPE_NONE;
922 wv->help = Qnil;
923 save_wv = wv;
924 prev_wv = 0;
925 }
926 else if (first_pane)
927 {
928 save_wv = wv;
929 prev_wv = 0;
930 }
931 first_pane = 0;
932 i += MENU_ITEMS_PANE_LENGTH;
933 }
934 else
935 {
936 /* Create a new item within current pane. */
937 Lisp_Object item_name, enable, descrip, def, type, selected, help;
938 item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
939 enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
940 descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
941 def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
942 type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
943 selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
944 help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
945
946 #ifndef HAVE_MULTILINGUAL_MENU
947 if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
948 {
949 item_name = ENCODE_MENU_STRING (item_name);
950 ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
951 }
952
953 if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
954 {
955 descrip = ENCODE_MENU_STRING (descrip);
956 ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
957 }
958 #endif /* not HAVE_MULTILINGUAL_MENU */
959
960 wv = xmalloc_widget_value ();
961 if (prev_wv)
962 prev_wv->next = wv;
963 else
964 save_wv->contents = wv;
965 wv->name = SSDATA (item_name);
966 if (!NILP (descrip))
967 wv->key = SSDATA (descrip);
968 wv->value = 0;
969 /* If this item has a null value,
970 make the call_data null so that it won't display a box
971 when the mouse is on it. */
972 wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
973 wv->enabled = !NILP (enable);
974
975 if (NILP (type))
976 wv->button_type = BUTTON_TYPE_NONE;
977 else if (EQ (type, QCtoggle))
978 wv->button_type = BUTTON_TYPE_TOGGLE;
979 else if (EQ (type, QCradio))
980 wv->button_type = BUTTON_TYPE_RADIO;
981 else
982 emacs_abort ();
983
984 wv->selected = !NILP (selected);
985
986 if (! STRINGP (help))
987 help = Qnil;
988
989 wv->help = help;
990
991 prev_wv = wv;
992
993 i += MENU_ITEMS_ITEM_LENGTH;
994 }
995 }
996 }
997 #endif
998
999 if (!NILP (title))
1000 {
1001 widget_value *wv_title = xmalloc_widget_value ();
1002 widget_value *wv_sep = xmalloc_widget_value ();
1003
1004 /* Maybe replace this separator with a bitmap or owner-draw item
1005 so that it looks better. Having two separators looks odd. */
1006 wv_sep->name = "--";
1007 wv_sep->next = first_wv->contents;
1008 wv_sep->help = Qnil;
1009
1010 #ifndef HAVE_MULTILINGUAL_MENU
1011 if (STRING_MULTIBYTE (title))
1012 title = ENCODE_MENU_STRING (title);
1013 #endif
1014
1015 wv_title->name = SSDATA (title);
1016 wv_title->enabled = NO;
1017 wv_title->button_type = BUTTON_TYPE_NONE;
1018 wv_title->help = Qnil;
1019 wv_title->next = wv_sep;
1020 first_wv->contents = wv_title;
1021 }
1022
1023 pmenu = [[EmacsMenu alloc] initWithTitle:
1024 [NSString stringWithUTF8String: SSDATA (title)]];
1025 [pmenu fillWithWidgetValue: first_wv->contents];
1026 free_menubar_widget_value_tree (first_wv);
1027 unbind_to (specpdl_count, Qnil);
1028
1029 popup_activated_flag = 1;
1030 tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1031 popup_activated_flag = 0;
1032 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1033
1034 return tem;
1035 }
1036
1037
1038 /* ==========================================================================
1039
1040 Toolbar: externally-called functions
1041
1042 ========================================================================== */
1043
1044 void
1045 free_frame_tool_bar (struct frame *f)
1046 /* --------------------------------------------------------------------------
1047 Under NS we just hide the toolbar until it might be needed again.
1048 -------------------------------------------------------------------------- */
1049 {
1050 block_input ();
1051 [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1052 FRAME_TOOLBAR_HEIGHT (f) = 0;
1053 unblock_input ();
1054 }
1055
1056 void
1057 update_frame_tool_bar (struct frame *f)
1058 /* --------------------------------------------------------------------------
1059 Update toolbar contents
1060 -------------------------------------------------------------------------- */
1061 {
1062 int i, k = 0;
1063 EmacsView *view = FRAME_NS_VIEW (f);
1064 NSWindow *window = [view window];
1065 EmacsToolbar *toolbar = [view toolbar];
1066
1067 block_input ();
1068
1069 #ifdef NS_IMPL_COCOA
1070 [toolbar clearActive];
1071 #else
1072 [toolbar clearAll];
1073 #endif
1074
1075 /* update EmacsToolbar as in GtkUtils, build items list */
1076 for (i = 0; i < f->n_tool_bar_items; ++i)
1077 {
1078 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1079 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1080
1081 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1082 int idx;
1083 ptrdiff_t img_id;
1084 struct image *img;
1085 Lisp_Object image;
1086 Lisp_Object helpObj;
1087 const char *helpText;
1088
1089 /* Check if this is a separator. */
1090 if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1091 {
1092 /* Skip separators. Newer OSX don't show them, and on GNUStep they
1093 are wide as a button, thus overflowing the toolbar most of
1094 the time. */
1095 continue;
1096 }
1097
1098 /* If image is a vector, choose the image according to the
1099 button state. */
1100 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1101 if (VECTORP (image))
1102 {
1103 /* NS toolbar auto-computes disabled and selected images */
1104 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1105 eassert (ASIZE (image) >= idx);
1106 image = AREF (image, idx);
1107 }
1108 else
1109 {
1110 idx = -1;
1111 }
1112 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1113 if (NILP (helpObj))
1114 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1115 helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1116
1117 /* Ignore invalid image specifications. */
1118 if (!valid_image_p (image))
1119 {
1120 /* Don't log anything, GNUS makes invalid images all the time. */
1121 continue;
1122 }
1123
1124 img_id = lookup_image (f, image);
1125 img = IMAGE_FROM_ID (f, img_id);
1126 prepare_image_for_display (f, img);
1127
1128 if (img->load_failed_p || img->pixmap == nil)
1129 {
1130 NSLog (@"Could not prepare toolbar image for display.");
1131 continue;
1132 }
1133
1134 [toolbar addDisplayItemWithImage: img->pixmap
1135 idx: k++
1136 tag: i
1137 helpText: helpText
1138 enabled: enabled_p];
1139 #undef TOOLPROP
1140 }
1141
1142 if (![toolbar isVisible])
1143 [toolbar setVisible: YES];
1144
1145 #ifdef NS_IMPL_COCOA
1146 if ([toolbar changed])
1147 {
1148 /* inform app that toolbar has changed */
1149 NSDictionary *dict = [toolbar configurationDictionary];
1150 NSMutableDictionary *newDict = [dict mutableCopy];
1151 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1152 id key;
1153 while ((key = [keys nextObject]) != nil)
1154 {
1155 NSObject *val = [dict objectForKey: key];
1156 if ([val isKindOfClass: [NSArray class]])
1157 {
1158 [newDict setObject:
1159 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1160 forKey: key];
1161 break;
1162 }
1163 }
1164 [toolbar setConfigurationFromDictionary: newDict];
1165 [newDict release];
1166 }
1167 #endif
1168
1169 FRAME_TOOLBAR_HEIGHT (f) =
1170 NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1171 - FRAME_NS_TITLEBAR_HEIGHT (f);
1172 if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1173 FRAME_TOOLBAR_HEIGHT (f) = 0;
1174 unblock_input ();
1175 }
1176
1177
1178 /* ==========================================================================
1179
1180 Toolbar: class implementation
1181
1182 ========================================================================== */
1183
1184 @implementation EmacsToolbar
1185
1186 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1187 {
1188 self = [super initWithIdentifier: identifier];
1189 emacsView = view;
1190 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1191 [self setSizeMode: NSToolbarSizeModeSmall];
1192 [self setDelegate: self];
1193 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1194 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1195 prevIdentifiers = nil;
1196 prevEnablement = enablement = 0L;
1197 return self;
1198 }
1199
1200 - (void)dealloc
1201 {
1202 [prevIdentifiers release];
1203 [activeIdentifiers release];
1204 [identifierToItem release];
1205 [super dealloc];
1206 }
1207
1208 - (void) clearActive
1209 {
1210 [prevIdentifiers release];
1211 prevIdentifiers = [activeIdentifiers copy];
1212 [activeIdentifiers removeAllObjects];
1213 prevEnablement = enablement;
1214 enablement = 0L;
1215 }
1216
1217 - (void) clearAll
1218 {
1219 [self clearActive];
1220 while ([[self items] count] > 0)
1221 [self removeItemAtIndex: 0];
1222 }
1223
1224 - (BOOL) changed
1225 {
1226 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1227 enablement == prevEnablement ? NO : YES;
1228 }
1229
1230 - (void) addDisplayItemWithImage: (EmacsImage *)img
1231 idx: (int)idx
1232 tag: (int)tag
1233 helpText: (const char *)help
1234 enabled: (BOOL)enabled
1235 {
1236 /* 1) come up w/identifier */
1237 NSString *identifier
1238 = [NSString stringWithFormat: @"%u", [img hash]];
1239 [activeIdentifiers addObject: identifier];
1240
1241 /* 2) create / reuse item */
1242 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1243 if (item == nil)
1244 {
1245 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1246 autorelease];
1247 [item setImage: img];
1248 [item setToolTip: [NSString stringWithUTF8String: help]];
1249 [item setTarget: emacsView];
1250 [item setAction: @selector (toolbarClicked:)];
1251 [identifierToItem setObject: item forKey: identifier];
1252 }
1253
1254 #ifdef NS_IMPL_GNUSTEP
1255 [self insertItemWithItemIdentifier: identifier atIndex: idx];
1256 #endif
1257
1258 [item setTag: tag];
1259 [item setEnabled: enabled];
1260
1261 /* 3) update state */
1262 enablement = (enablement << 1) | (enabled == YES);
1263 }
1264
1265 /* This overrides super's implementation, which automatically sets
1266 all items to enabled state (for some reason). */
1267 - (void)validateVisibleItems
1268 {
1269 }
1270
1271
1272 /* delegate methods */
1273
1274 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1275 itemForItemIdentifier: (NSString *)itemIdentifier
1276 willBeInsertedIntoToolbar: (BOOL)flag
1277 {
1278 /* look up NSToolbarItem by identifier and return... */
1279 return [identifierToItem objectForKey: itemIdentifier];
1280 }
1281
1282 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1283 {
1284 /* return entire set.. */
1285 return activeIdentifiers;
1286 }
1287
1288 /* for configuration palette (not yet supported) */
1289 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1290 {
1291 /* return entire set... */
1292 return activeIdentifiers;
1293 //return [identifierToItem allKeys];
1294 }
1295
1296 /* optional and unneeded */
1297 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1298 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1299 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1300
1301 @end /* EmacsToolbar */
1302
1303
1304
1305 /* ==========================================================================
1306
1307 Tooltip: class implementation
1308
1309 ========================================================================== */
1310
1311 /* Needed because NeXTstep does not provide enough control over tooltip
1312 display. */
1313 @implementation EmacsTooltip
1314
1315 - init
1316 {
1317 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1318 blue: 0.792 alpha: 0.95];
1319 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1320 NSFont *sfont = [font screenFont];
1321 int height = [sfont ascender] - [sfont descender];
1322 /*[font boundingRectForFont].size.height; */
1323 NSRect r = NSMakeRect (0, 0, 100, height+6);
1324
1325 textField = [[NSTextField alloc] initWithFrame: r];
1326 [textField setFont: font];
1327 [textField setBackgroundColor: col];
1328
1329 [textField setEditable: NO];
1330 [textField setSelectable: NO];
1331 [textField setBordered: NO];
1332 [textField setBezeled: NO];
1333 [textField setDrawsBackground: YES];
1334
1335 win = [[NSWindow alloc]
1336 initWithContentRect: [textField frame]
1337 styleMask: 0
1338 backing: NSBackingStoreBuffered
1339 defer: YES];
1340 [win setHasShadow: YES];
1341 [win setReleasedWhenClosed: NO];
1342 [win setDelegate: self];
1343 [[win contentView] addSubview: textField];
1344 /* [win setBackgroundColor: col]; */
1345 [win setOpaque: NO];
1346
1347 return self;
1348 }
1349
1350 - (void) dealloc
1351 {
1352 [win close];
1353 [win release];
1354 [textField release];
1355 [super dealloc];
1356 }
1357
1358 - (void) setText: (char *)text
1359 {
1360 NSString *str = [NSString stringWithUTF8String: text];
1361 NSRect r = [textField frame];
1362 NSSize tooltipDims;
1363
1364 [textField setStringValue: str];
1365 tooltipDims = [[textField cell] cellSize];
1366
1367 r.size.width = tooltipDims.width;
1368 r.size.height = tooltipDims.height;
1369 [textField setFrame: r];
1370 }
1371
1372 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1373 {
1374 NSRect wr = [win frame];
1375
1376 wr.origin = NSMakePoint (x, y);
1377 wr.size = [textField frame].size;
1378
1379 [win setFrame: wr display: YES];
1380 [win setLevel: NSPopUpMenuWindowLevel];
1381 [win orderFront: self];
1382 [win display];
1383 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1384 selector: @selector (hide)
1385 userInfo: nil repeats: NO];
1386 [timer retain];
1387 }
1388
1389 - (void) hide
1390 {
1391 [win close];
1392 if (timer != nil)
1393 {
1394 if ([timer isValid])
1395 [timer invalidate];
1396 [timer release];
1397 timer = nil;
1398 }
1399 }
1400
1401 - (BOOL) isActive
1402 {
1403 return timer != nil;
1404 }
1405
1406 - (NSRect) frame
1407 {
1408 return [textField frame];
1409 }
1410
1411 @end /* EmacsTooltip */
1412
1413
1414
1415 /* ==========================================================================
1416
1417 Popup Dialog: implementing functions
1418
1419 ========================================================================== */
1420
1421 struct Popdown_data
1422 {
1423 NSAutoreleasePool *pool;
1424 EmacsDialogPanel *dialog;
1425 };
1426
1427 static void
1428 pop_down_menu (void *arg)
1429 {
1430 struct Popdown_data *unwind_data = arg;
1431
1432 block_input ();
1433 if (popup_activated_flag)
1434 {
1435 EmacsDialogPanel *panel = unwind_data->dialog;
1436 popup_activated_flag = 0;
1437 [panel close];
1438 [unwind_data->pool release];
1439 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1440 }
1441
1442 xfree (unwind_data);
1443 unblock_input ();
1444 }
1445
1446
1447 Lisp_Object
1448 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1449 {
1450 id dialog;
1451 Lisp_Object window, tem, title;
1452 struct frame *f;
1453 NSPoint p;
1454 BOOL isQ;
1455 NSAutoreleasePool *pool;
1456
1457 NSTRACE (x-popup-dialog);
1458
1459 isQ = NILP (header);
1460
1461 if (EQ (position, Qt)
1462 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1463 || EQ (XCAR (position), Qtool_bar))))
1464 {
1465 window = selected_window;
1466 }
1467 else if (CONSP (position))
1468 {
1469 Lisp_Object tem;
1470 tem = Fcar (position);
1471 if (XTYPE (tem) == Lisp_Cons)
1472 window = Fcar (Fcdr (position));
1473 else
1474 {
1475 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1476 window = Fcar (tem); /* POSN_WINDOW (tem) */
1477 }
1478 }
1479 else if (WINDOWP (position) || FRAMEP (position))
1480 {
1481 window = position;
1482 }
1483 else
1484 window = Qnil;
1485
1486 if (FRAMEP (window))
1487 f = XFRAME (window);
1488 else if (WINDOWP (window))
1489 {
1490 CHECK_LIVE_WINDOW (window);
1491 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1492 }
1493 else
1494 CHECK_WINDOW (window);
1495
1496 check_window_system (f);
1497
1498 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1499 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1500
1501 title = Fcar (contents);
1502 CHECK_STRING (title);
1503
1504 if (NILP (Fcar (Fcdr (contents))))
1505 /* No buttons specified, add an "Ok" button so users can pop down
1506 the dialog. */
1507 contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1508
1509 block_input ();
1510 pool = [[NSAutoreleasePool alloc] init];
1511 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1512 isQuestion: isQ];
1513
1514 {
1515 ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1516 struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1517
1518 unwind_data->pool = pool;
1519 unwind_data->dialog = dialog;
1520
1521 record_unwind_protect_ptr (pop_down_menu, unwind_data);
1522 popup_activated_flag = 1;
1523 tem = [dialog runDialogAt: p];
1524 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
1525 }
1526
1527 unblock_input ();
1528
1529 return tem;
1530 }
1531
1532
1533 /* ==========================================================================
1534
1535 Popup Dialog: class implementation
1536
1537 ========================================================================== */
1538
1539 @interface FlippedView : NSView
1540 {
1541 }
1542 @end
1543
1544 @implementation FlippedView
1545 - (BOOL)isFlipped
1546 {
1547 return YES;
1548 }
1549 @end
1550
1551 @implementation EmacsDialogPanel
1552
1553 #define SPACER 8.0
1554 #define ICONSIZE 64.0
1555 #define TEXTHEIGHT 20.0
1556 #define MINCELLWIDTH 90.0
1557
1558 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1559 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1560 {
1561 NSSize spacing = {SPACER, SPACER};
1562 NSRect area;
1563 id cell;
1564 NSImageView *imgView;
1565 FlippedView *contentView;
1566 NSImage *img;
1567
1568 dialog_return = Qundefined;
1569 button_values = NULL;
1570 area.origin.x = 3*SPACER;
1571 area.origin.y = 2*SPACER;
1572 area.size.width = ICONSIZE;
1573 area.size.height= ICONSIZE;
1574 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1575 [img setScalesWhenResized: YES];
1576 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1577 imgView = [[NSImageView alloc] initWithFrame: area];
1578 [imgView setImage: img];
1579 [imgView setEditable: NO];
1580 [img autorelease];
1581 [imgView autorelease];
1582
1583 aStyle = NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask;
1584 flag = YES;
1585 rows = 0;
1586 cols = 1;
1587 [super initWithContentRect: contentRect styleMask: aStyle
1588 backing: backingType defer: flag];
1589 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1590 [contentView autorelease];
1591
1592 [self setContentView: contentView];
1593
1594 [[self contentView] setAutoresizesSubviews: YES];
1595
1596 [[self contentView] addSubview: imgView];
1597 [self setTitle: @""];
1598
1599 area.origin.x += ICONSIZE+2*SPACER;
1600 /* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1601 area.size.width = 400;
1602 area.size.height= TEXTHEIGHT;
1603 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1604 [[self contentView] addSubview: command];
1605 [command setStringValue: ns_app_name];
1606 [command setDrawsBackground: NO];
1607 [command setBezeled: NO];
1608 [command setSelectable: NO];
1609 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1610
1611 /* area.origin.x = ICONSIZE+2*SPACER;
1612 area.origin.y = TEXTHEIGHT + 2*SPACER;
1613 area.size.width = 400;
1614 area.size.height= 2;
1615 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1616 [[self contentView] addSubview: tem];
1617 [tem setTitlePosition: NSNoTitle];
1618 [tem setAutoresizingMask: NSViewWidthSizable];*/
1619
1620 /* area.origin.x = ICONSIZE+2*SPACER; */
1621 area.origin.y += TEXTHEIGHT+SPACER;
1622 area.size.width = 400;
1623 area.size.height= TEXTHEIGHT;
1624 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1625 [[self contentView] addSubview: title];
1626 [title setDrawsBackground: NO];
1627 [title setBezeled: NO];
1628 [title setSelectable: NO];
1629 [title setFont: [NSFont systemFontOfSize: 11.0]];
1630
1631 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1632 [cell setBordered: NO];
1633 [cell setEnabled: NO];
1634 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1635 [cell setBezelStyle: NSRoundedBezelStyle];
1636
1637 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1638 mode: NSHighlightModeMatrix
1639 prototype: cell
1640 numberOfRows: 0
1641 numberOfColumns: 1];
1642 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1643 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1644 [matrix setIntercellSpacing: spacing];
1645 [matrix autorelease];
1646
1647 [[self contentView] addSubview: matrix];
1648 [self setOneShot: YES];
1649 [self setReleasedWhenClosed: YES];
1650 [self setHidesOnDeactivate: YES];
1651 return self;
1652 }
1653
1654
1655 - (BOOL)windowShouldClose: (id)sender
1656 {
1657 window_closed = YES;
1658 [NSApp stop:self];
1659 return NO;
1660 }
1661
1662 - (void)dealloc
1663 {
1664 xfree (button_values);
1665 [super dealloc];
1666 }
1667
1668 - (void)process_dialog: (Lisp_Object) list
1669 {
1670 Lisp_Object item, lst = list;
1671 int row = 0;
1672 int buttons = 0, btnnr = 0;
1673
1674 for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1675 {
1676 item = XCAR (list);
1677 if (XTYPE (item) == Lisp_Cons)
1678 ++buttons;
1679 }
1680
1681 if (buttons > 0)
1682 button_values = xmalloc (buttons * sizeof *button_values);
1683
1684 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1685 {
1686 item = XCAR (list);
1687 if (XTYPE (item) == Lisp_String)
1688 {
1689 [self addString: SSDATA (item) row: row++];
1690 }
1691 else if (XTYPE (item) == Lisp_Cons)
1692 {
1693 button_values[btnnr] = XCDR (item);
1694 [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1695 ++btnnr;
1696 }
1697 else if (NILP (item))
1698 {
1699 [self addSplit];
1700 row = 0;
1701 }
1702 }
1703 }
1704
1705
1706 - (void)addButton: (char *)str value: (int)tag row: (int)row
1707 {
1708 id cell;
1709
1710 if (row >= rows)
1711 {
1712 [matrix addRow];
1713 rows++;
1714 }
1715 cell = [matrix cellAtRow: row column: cols-1];
1716 [cell setTarget: self];
1717 [cell setAction: @selector (clicked: )];
1718 [cell setTitle: [NSString stringWithUTF8String: str]];
1719 [cell setTag: tag];
1720 [cell setBordered: YES];
1721 [cell setEnabled: YES];
1722 }
1723
1724
1725 - (void)addString: (char *)str row: (int)row
1726 {
1727 id cell;
1728
1729 if (row >= rows)
1730 {
1731 [matrix addRow];
1732 rows++;
1733 }
1734 cell = [matrix cellAtRow: row column: cols-1];
1735 [cell setTitle: [NSString stringWithUTF8String: str]];
1736 [cell setBordered: YES];
1737 [cell setEnabled: NO];
1738 }
1739
1740
1741 - (void)addSplit
1742 {
1743 [matrix addColumn];
1744 cols++;
1745 }
1746
1747
1748 - (void)clicked: sender
1749 {
1750 NSArray *sellist = nil;
1751 EMACS_INT seltag;
1752
1753 sellist = [sender selectedCells];
1754 if ([sellist count] < 1)
1755 return;
1756
1757 seltag = [[sellist objectAtIndex: 0] tag];
1758 dialog_return = button_values[seltag];
1759 [NSApp stop:self];
1760 }
1761
1762
1763 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1764 {
1765 Lisp_Object head;
1766 [super init];
1767
1768 if (XTYPE (contents) == Lisp_Cons)
1769 {
1770 head = Fcar (contents);
1771 [self process_dialog: Fcdr (contents)];
1772 }
1773 else
1774 head = contents;
1775
1776 if (XTYPE (head) == Lisp_String)
1777 [title setStringValue:
1778 [NSString stringWithUTF8String: SSDATA (head)]];
1779 else if (isQ == YES)
1780 [title setStringValue: @"Question"];
1781 else
1782 [title setStringValue: @"Information"];
1783
1784 {
1785 int i;
1786 NSRect r, s, t;
1787
1788 if (cols == 1 && rows > 1) /* Never told where to split */
1789 {
1790 [matrix addColumn];
1791 for (i = 0; i < rows/2; i++)
1792 {
1793 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1794 atRow: i column: 1];
1795 [matrix removeRow: (rows+1)/2];
1796 }
1797 }
1798
1799 [matrix sizeToFit];
1800 {
1801 NSSize csize = [matrix cellSize];
1802 if (csize.width < MINCELLWIDTH)
1803 {
1804 csize.width = MINCELLWIDTH;
1805 [matrix setCellSize: csize];
1806 [matrix sizeToCells];
1807 }
1808 }
1809
1810 [title sizeToFit];
1811 [command sizeToFit];
1812
1813 t = [matrix frame];
1814 r = [title frame];
1815 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1816 {
1817 t.origin.x = r.origin.x;
1818 t.size.width = r.size.width;
1819 }
1820 r = [command frame];
1821 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1822 {
1823 t.origin.x = r.origin.x;
1824 t.size.width = r.size.width;
1825 }
1826
1827 r = [self frame];
1828 s = [(NSView *)[self contentView] frame];
1829 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1830 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1831 [self setFrame: r display: NO];
1832 }
1833
1834 return self;
1835 }
1836
1837
1838
1839 - (void)timeout_handler: (NSTimer *)timedEntry
1840 {
1841 NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1842 location: NSMakePoint (0, 0)
1843 modifierFlags: 0
1844 timestamp: 0
1845 windowNumber: [[NSApp mainWindow] windowNumber]
1846 context: [NSApp context]
1847 subtype: 0
1848 data1: 0
1849 data2: 0];
1850
1851 timer_fired = YES;
1852 /* We use sto because stopModal/abortModal out of the main loop does not
1853 seem to work in 10.6. But as we use stop we must send a real event so
1854 the stop is seen and acted upon. */
1855 [NSApp stop:self];
1856 [NSApp postEvent: nxev atStart: NO];
1857 }
1858
1859 - (Lisp_Object)runDialogAt: (NSPoint)p
1860 {
1861 Lisp_Object ret = Qundefined;
1862
1863 while (popup_activated_flag)
1864 {
1865 NSTimer *tmo = nil;
1866 EMACS_TIME next_time = timer_check ();
1867
1868 if (EMACS_TIME_VALID_P (next_time))
1869 {
1870 double time = EMACS_TIME_TO_DOUBLE (next_time);
1871 tmo = [NSTimer timerWithTimeInterval: time
1872 target: self
1873 selector: @selector (timeout_handler:)
1874 userInfo: 0
1875 repeats: NO];
1876 [[NSRunLoop currentRunLoop] addTimer: tmo
1877 forMode: NSModalPanelRunLoopMode];
1878 }
1879 timer_fired = NO;
1880 dialog_return = Qundefined;
1881 [NSApp runModalForWindow: self];
1882 ret = dialog_return;
1883 if (! timer_fired)
1884 {
1885 if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1886 break;
1887 }
1888 }
1889
1890 if (EQ (ret, Qundefined) && window_closed)
1891 /* Make close button pressed equivalent to C-g. */
1892 Fsignal (Qquit, Qnil);
1893
1894 return ret;
1895 }
1896
1897 @end
1898
1899
1900 /* ==========================================================================
1901
1902 Lisp definitions
1903
1904 ========================================================================== */
1905
1906 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1907 doc: /* Cause the NS menu to be re-calculated. */)
1908 (void)
1909 {
1910 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1911 return Qnil;
1912 }
1913
1914
1915 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1916 doc: /* Pop up a dialog box and return user's selection.
1917 POSITION specifies which frame to use.
1918 This is normally a mouse button event or a window or frame.
1919 If POSITION is t, it means to use the frame the mouse is on.
1920 The dialog box appears in the middle of the specified frame.
1921
1922 CONTENTS specifies the alternatives to display in the dialog box.
1923 It is a list of the form (DIALOG ITEM1 ITEM2...).
1924 Each ITEM is a cons cell (STRING . VALUE).
1925 The return value is VALUE from the chosen item.
1926
1927 An ITEM may also be just a string--that makes a nonselectable item.
1928 An ITEM may also be nil--that means to put all preceding items
1929 on the left of the dialog box and all following items on the right.
1930 \(By default, approximately half appear on each side.)
1931
1932 If HEADER is non-nil, the frame title for the box is "Information",
1933 otherwise it is "Question".
1934
1935 If the user gets rid of the dialog box without making a valid choice,
1936 for instance using the window manager, then this produces a quit and
1937 `x-popup-dialog' does not return. */)
1938 (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1939 {
1940 return ns_popup_dialog (position, contents, header);
1941 }
1942
1943 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1944 doc: /* Return t if a menu or popup dialog is active. */)
1945 (void)
1946 {
1947 return popup_activated () ? Qt : Qnil;
1948 }
1949
1950 /* ==========================================================================
1951
1952 Lisp interface declaration
1953
1954 ========================================================================== */
1955
1956 void
1957 syms_of_nsmenu (void)
1958 {
1959 #ifndef NS_IMPL_COCOA
1960 /* Don't know how to keep track of this in Next/Open/Gnustep. Always
1961 update menus there. */
1962 trackingMenu = 1;
1963 #endif
1964 defsubr (&Sx_popup_dialog);
1965 defsubr (&Sns_reset_menu);
1966 defsubr (&Smenu_or_popup_active_p);
1967
1968 Qdebug_on_next_call = intern_c_string ("debug-on-next-call");
1969 staticpro (&Qdebug_on_next_call);
1970 }