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