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