]> code.delx.au - gnu-emacs/blob - src/nsmenu.m
Update copyright year to 2015
[gnu-emacs] / src / nsmenu.m
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2 Copyright (C) 2007-2015 Free Software Foundation, Inc.
3
4 This file is part of GNU Emacs.
5
6 GNU Emacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 GNU Emacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
18
19 /*
20 By Adrian Robert, based on code from original nsmenu.m (Carl Edman,
21 Christian Limpach, Scott Bender, Christophe de Dinechin) and code in the
22 Carbon version by Yamamoto Mitsuharu. */
23
24 /* This should be the first include, as it may set up #defines affecting
25 interpretation of even the system includes. */
26 #include <config.h>
27
28 #include "lisp.h"
29 #include "window.h"
30 #include "character.h"
31 #include "buffer.h"
32 #include "keymap.h"
33 #include "coding.h"
34 #include "commands.h"
35 #include "blockinput.h"
36 #include "nsterm.h"
37 #include "termhooks.h"
38 #include "keyboard.h"
39 #include "menu.h"
40
41 #define NSMENUPROFILE 0
42
43 #if NSMENUPROFILE
44 #include <sys/timeb.h>
45 #include <sys/types.h>
46 #endif
47
48 #if 0
49 int menu_trace_num = 0;
50 #define NSTRACE(x) fprintf (stderr, "%s:%d: [%d] " #x "\n", \
51 __FILE__, __LINE__, ++menu_trace_num)
52 #else
53 #define NSTRACE(x)
54 #endif
55
56 #if 0
57 /* Include lisp -> C common menu parsing code */
58 #define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str)
59 #include "nsmenu_common.c"
60 #endif
61
62 extern 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 ([ns_app_name isEqualToString: title]
697 && ![item isSeparatorItem])
698 continue;
699 [self removeItemAtIndex: n];
700 }
701 }
702
703
704 - (void)fillWithWidgetValue: (void *)wvptr
705 {
706 [self fillWithWidgetValue: wvptr frame: (struct frame *)nil];
707 }
708
709 - (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f
710 {
711 widget_value *wv = (widget_value *)wvptr;
712
713 /* clear existing contents */
714 [self setMenuChangedMessagesEnabled: NO];
715 [self clear];
716
717 /* add new contents */
718 for (; wv != NULL; wv = wv->next)
719 {
720 NSMenuItem *item = [self addItemWithWidgetValue: wv];
721
722 if (wv->contents)
723 {
724 EmacsMenu *submenu;
725
726 if (f)
727 submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f];
728 else
729 submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
730
731 [self setSubmenu: submenu forItem: item];
732 [submenu fillWithWidgetValue: wv->contents];
733 [submenu release];
734 [item setAction: (SEL)nil];
735 }
736 }
737
738 [self setMenuChangedMessagesEnabled: YES];
739 #ifdef NS_IMPL_GNUSTEP
740 if ([[self window] isVisible])
741 [self sizeToFit];
742 #endif
743 }
744
745
746 /* adds an empty submenu and returns it */
747 - (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f
748 {
749 NSString *titleStr = [NSString stringWithUTF8String: title];
750 NSMenuItem *item = [self addItemWithTitle: titleStr
751 action: (SEL)nil /*@selector (menuDown:) */
752 keyEquivalent: @""];
753 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
754 [self setSubmenu: submenu forItem: item];
755 [submenu release];
756 return submenu;
757 }
758
759 /* run a menu in popup mode */
760 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
761 keymaps: (bool)keymaps
762 {
763 EmacsView *view = FRAME_NS_VIEW (f);
764 NSEvent *e, *event;
765 long retVal;
766
767 /* p = [view convertPoint:p fromView: nil]; */
768 p.y = NSHeight ([view frame]) - p.y;
769 e = [[view window] currentEvent];
770 event = [NSEvent mouseEventWithType: NSRightMouseDown
771 location: p
772 modifierFlags: 0
773 timestamp: [e timestamp]
774 windowNumber: [[view window] windowNumber]
775 context: [e context]
776 eventNumber: 0/*[e eventNumber] */
777 clickCount: 1
778 pressure: 0];
779
780 context_menu_value = -1;
781 [NSMenu popUpContextMenu: self withEvent: event forView: view];
782 retVal = context_menu_value;
783 context_menu_value = 0;
784 return retVal > 0
785 ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
786 : Qnil;
787 }
788
789 @end /* EmacsMenu */
790
791
792
793 /* ==========================================================================
794
795 Context Menu: implementing functions
796
797 ========================================================================== */
798
799 Lisp_Object
800 ns_menu_show (struct frame *f, int x, int y, int menuflags,
801 Lisp_Object title, const char **error)
802 {
803 EmacsMenu *pmenu;
804 NSPoint p;
805 Lisp_Object tem;
806 ptrdiff_t specpdl_count = SPECPDL_INDEX ();
807 widget_value *wv, *first_wv = 0;
808 bool keymaps = (menuflags & MENU_KEYMAPS);
809
810 block_input ();
811
812 p.x = x; p.y = y;
813
814 /* now parse stage 2 as in ns_update_menubar */
815 wv = make_widget_value ("contextmenu", NULL, true, Qnil);
816 wv->button_type = BUTTON_TYPE_NONE;
817 first_wv = wv;
818
819 #if 0
820 /* FIXME: a couple of one-line differences prevent reuse */
821 wv = digest_single_submenu (0, menu_items_used, 0);
822 #else
823 {
824 widget_value *save_wv = 0, *prev_wv = 0;
825 widget_value **submenu_stack
826 = alloca (menu_items_used * sizeof *submenu_stack);
827 /* Lisp_Object *subprefix_stack
828 = alloca (menu_items_used * sizeof *subprefix_stack); */
829 int submenu_depth = 0;
830 int first_pane = 1;
831 int i;
832
833 /* Loop over all panes and items, filling in the tree. */
834 i = 0;
835 while (i < menu_items_used)
836 {
837 if (EQ (AREF (menu_items, i), Qnil))
838 {
839 submenu_stack[submenu_depth++] = save_wv;
840 save_wv = prev_wv;
841 prev_wv = 0;
842 first_pane = 1;
843 i++;
844 }
845 else if (EQ (AREF (menu_items, i), Qlambda))
846 {
847 prev_wv = save_wv;
848 save_wv = submenu_stack[--submenu_depth];
849 first_pane = 0;
850 i++;
851 }
852 else if (EQ (AREF (menu_items, i), Qt)
853 && submenu_depth != 0)
854 i += MENU_ITEMS_PANE_LENGTH;
855 /* Ignore a nil in the item list.
856 It's meaningful only for dialog boxes. */
857 else if (EQ (AREF (menu_items, i), Qquote))
858 i += 1;
859 else if (EQ (AREF (menu_items, i), Qt))
860 {
861 /* Create a new pane. */
862 Lisp_Object pane_name, prefix;
863 const char *pane_string;
864
865 pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
866 prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
867
868 #ifndef HAVE_MULTILINGUAL_MENU
869 if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
870 {
871 pane_name = ENCODE_MENU_STRING (pane_name);
872 ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
873 }
874 #endif
875 pane_string = (NILP (pane_name)
876 ? "" : SSDATA (pane_name));
877 /* If there is just one top-level pane, put all its items directly
878 under the top-level menu. */
879 if (menu_items_n_panes == 1)
880 pane_string = "";
881
882 /* If the pane has a meaningful name,
883 make the pane a top-level menu item
884 with its items as a submenu beneath it. */
885 if (!keymaps && strcmp (pane_string, ""))
886 {
887 wv = make_widget_value (pane_string, NULL, true, Qnil);
888 if (save_wv)
889 save_wv->next = wv;
890 else
891 first_wv->contents = wv;
892 if (keymaps && !NILP (prefix))
893 wv->name++;
894 wv->button_type = BUTTON_TYPE_NONE;
895 save_wv = wv;
896 prev_wv = 0;
897 }
898 else if (first_pane)
899 {
900 save_wv = wv;
901 prev_wv = 0;
902 }
903 first_pane = 0;
904 i += MENU_ITEMS_PANE_LENGTH;
905 }
906 else
907 {
908 /* Create a new item within current pane. */
909 Lisp_Object item_name, enable, descrip, def, type, selected, help;
910 item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
911 enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
912 descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
913 def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
914 type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
915 selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
916 help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
917
918 #ifndef HAVE_MULTILINGUAL_MENU
919 if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
920 {
921 item_name = ENCODE_MENU_STRING (item_name);
922 ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
923 }
924
925 if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
926 {
927 descrip = ENCODE_MENU_STRING (descrip);
928 ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
929 }
930 #endif /* not HAVE_MULTILINGUAL_MENU */
931
932 wv = make_widget_value (SSDATA (item_name), NULL, !NILP (enable),
933 STRINGP (help) ? help : Qnil);
934 if (prev_wv)
935 prev_wv->next = wv;
936 else
937 save_wv->contents = wv;
938 if (!NILP (descrip))
939 wv->key = SSDATA (descrip);
940 /* If this item has a null value,
941 make the call_data null so that it won't display a box
942 when the mouse is on it. */
943 wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
944
945 if (NILP (type))
946 wv->button_type = BUTTON_TYPE_NONE;
947 else if (EQ (type, QCtoggle))
948 wv->button_type = BUTTON_TYPE_TOGGLE;
949 else if (EQ (type, QCradio))
950 wv->button_type = BUTTON_TYPE_RADIO;
951 else
952 emacs_abort ();
953
954 wv->selected = !NILP (selected);
955
956 prev_wv = wv;
957
958 i += MENU_ITEMS_ITEM_LENGTH;
959 }
960 }
961 }
962 #endif
963
964 if (!NILP (title))
965 {
966 widget_value *wv_title;
967 widget_value *wv_sep = make_widget_value ("--", NULL, false, Qnil);
968
969 /* Maybe replace this separator with a bitmap or owner-draw item
970 so that it looks better. Having two separators looks odd. */
971 wv_sep->next = first_wv->contents;
972
973 #ifndef HAVE_MULTILINGUAL_MENU
974 if (STRING_MULTIBYTE (title))
975 title = ENCODE_MENU_STRING (title);
976 #endif
977 wv_title = make_widget_value (SSDATA (title), NULL, false, Qnil);
978 wv_title->button_type = BUTTON_TYPE_NONE;
979 wv_title->next = wv_sep;
980 first_wv->contents = wv_title;
981 }
982
983 pmenu = [[EmacsMenu alloc] initWithTitle:
984 [NSString stringWithUTF8String: SSDATA (title)]];
985 [pmenu fillWithWidgetValue: first_wv->contents];
986 free_menubar_widget_value_tree (first_wv);
987 unbind_to (specpdl_count, Qnil);
988
989 popup_activated_flag = 1;
990 tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
991 popup_activated_flag = 0;
992 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
993
994 unblock_input ();
995 return tem;
996 }
997
998
999 /* ==========================================================================
1000
1001 Toolbar: externally-called functions
1002
1003 ========================================================================== */
1004
1005 void
1006 free_frame_tool_bar (struct frame *f)
1007 /* --------------------------------------------------------------------------
1008 Under NS we just hide the toolbar until it might be needed again.
1009 -------------------------------------------------------------------------- */
1010 {
1011 EmacsView *view = FRAME_NS_VIEW (f);
1012 block_input ();
1013 view->wait_for_tool_bar = NO;
1014 [[view toolbar] setVisible: NO];
1015 FRAME_TOOLBAR_HEIGHT (f) = 0;
1016 unblock_input ();
1017 }
1018
1019 void
1020 update_frame_tool_bar (struct frame *f)
1021 /* --------------------------------------------------------------------------
1022 Update toolbar contents
1023 -------------------------------------------------------------------------- */
1024 {
1025 int i, k = 0;
1026 EmacsView *view = FRAME_NS_VIEW (f);
1027 NSWindow *window = [view window];
1028 EmacsToolbar *toolbar = [view toolbar];
1029 int oldh;
1030
1031 if (view == nil || toolbar == nil) return;
1032 block_input ();
1033
1034 oldh = FRAME_TOOLBAR_HEIGHT (f);
1035
1036 #ifdef NS_IMPL_COCOA
1037 [toolbar clearActive];
1038 #else
1039 [toolbar clearAll];
1040 #endif
1041
1042 /* update EmacsToolbar as in GtkUtils, build items list */
1043 for (i = 0; i < f->n_tool_bar_items; ++i)
1044 {
1045 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1046 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1047
1048 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1049 int idx;
1050 ptrdiff_t img_id;
1051 struct image *img;
1052 Lisp_Object image;
1053 Lisp_Object helpObj;
1054 const char *helpText;
1055
1056 /* Check if this is a separator. */
1057 if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1058 {
1059 /* Skip separators. Newer OSX don't show them, and on GNUstep they
1060 are wide as a button, thus overflowing the toolbar most of
1061 the time. */
1062 continue;
1063 }
1064
1065 /* If image is a vector, choose the image according to the
1066 button state. */
1067 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1068 if (VECTORP (image))
1069 {
1070 /* NS toolbar auto-computes disabled and selected images */
1071 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1072 eassert (ASIZE (image) >= idx);
1073 image = AREF (image, idx);
1074 }
1075 else
1076 {
1077 idx = -1;
1078 }
1079 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1080 if (NILP (helpObj))
1081 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1082 helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1083
1084 /* Ignore invalid image specifications. */
1085 if (!valid_image_p (image))
1086 {
1087 /* Don't log anything, GNUS makes invalid images all the time. */
1088 continue;
1089 }
1090
1091 img_id = lookup_image (f, image);
1092 img = IMAGE_FROM_ID (f, img_id);
1093 prepare_image_for_display (f, img);
1094
1095 if (img->load_failed_p || img->pixmap == nil)
1096 {
1097 NSLog (@"Could not prepare toolbar image for display.");
1098 continue;
1099 }
1100
1101 [toolbar addDisplayItemWithImage: img->pixmap
1102 idx: k++
1103 tag: i
1104 helpText: helpText
1105 enabled: enabled_p];
1106 #undef TOOLPROP
1107 }
1108
1109 if (![toolbar isVisible])
1110 [toolbar setVisible: YES];
1111
1112 #ifdef NS_IMPL_COCOA
1113 if ([toolbar changed])
1114 {
1115 /* inform app that toolbar has changed */
1116 NSDictionary *dict = [toolbar configurationDictionary];
1117 NSMutableDictionary *newDict = [dict mutableCopy];
1118 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1119 id key;
1120 while ((key = [keys nextObject]) != nil)
1121 {
1122 NSObject *val = [dict objectForKey: key];
1123 if ([val isKindOfClass: [NSArray class]])
1124 {
1125 [newDict setObject:
1126 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1127 forKey: key];
1128 break;
1129 }
1130 }
1131 [toolbar setConfigurationFromDictionary: newDict];
1132 [newDict release];
1133 }
1134 #endif
1135
1136 FRAME_TOOLBAR_HEIGHT (f) =
1137 NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1138 - FRAME_NS_TITLEBAR_HEIGHT (f);
1139 if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1140 FRAME_TOOLBAR_HEIGHT (f) = 0;
1141
1142 if (oldh != FRAME_TOOLBAR_HEIGHT (f))
1143 [view updateFrameSize:YES];
1144 if (view->wait_for_tool_bar && FRAME_TOOLBAR_HEIGHT (f) > 0)
1145 {
1146 view->wait_for_tool_bar = NO;
1147 [view setNeedsDisplay: YES];
1148 }
1149
1150 unblock_input ();
1151 }
1152
1153
1154 /* ==========================================================================
1155
1156 Toolbar: class implementation
1157
1158 ========================================================================== */
1159
1160 @implementation EmacsToolbar
1161
1162 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1163 {
1164 self = [super initWithIdentifier: identifier];
1165 emacsView = view;
1166 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1167 [self setSizeMode: NSToolbarSizeModeSmall];
1168 [self setDelegate: self];
1169 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1170 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1171 prevIdentifiers = nil;
1172 prevEnablement = enablement = 0L;
1173 return self;
1174 }
1175
1176 - (void)dealloc
1177 {
1178 [prevIdentifiers release];
1179 [activeIdentifiers release];
1180 [identifierToItem release];
1181 [super dealloc];
1182 }
1183
1184 - (void) clearActive
1185 {
1186 [prevIdentifiers release];
1187 prevIdentifiers = [activeIdentifiers copy];
1188 [activeIdentifiers removeAllObjects];
1189 prevEnablement = enablement;
1190 enablement = 0L;
1191 }
1192
1193 - (void) clearAll
1194 {
1195 [self clearActive];
1196 while ([[self items] count] > 0)
1197 [self removeItemAtIndex: 0];
1198 }
1199
1200 - (BOOL) changed
1201 {
1202 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1203 enablement == prevEnablement ? NO : YES;
1204 }
1205
1206 - (void) addDisplayItemWithImage: (EmacsImage *)img
1207 idx: (int)idx
1208 tag: (int)tag
1209 helpText: (const char *)help
1210 enabled: (BOOL)enabled
1211 {
1212 /* 1) come up w/identifier */
1213 NSString *identifier
1214 = [NSString stringWithFormat: @"%lu", (unsigned long)[img hash]];
1215 [activeIdentifiers addObject: identifier];
1216
1217 /* 2) create / reuse item */
1218 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1219 if (item == nil)
1220 {
1221 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1222 autorelease];
1223 [item setImage: img];
1224 [item setToolTip: [NSString stringWithUTF8String: help]];
1225 [item setTarget: emacsView];
1226 [item setAction: @selector (toolbarClicked:)];
1227 [identifierToItem setObject: item forKey: identifier];
1228 }
1229
1230 #ifdef NS_IMPL_GNUSTEP
1231 [self insertItemWithItemIdentifier: identifier atIndex: idx];
1232 #endif
1233
1234 [item setTag: tag];
1235 [item setEnabled: enabled];
1236
1237 /* 3) update state */
1238 enablement = (enablement << 1) | (enabled == YES);
1239 }
1240
1241 /* This overrides super's implementation, which automatically sets
1242 all items to enabled state (for some reason). */
1243 - (void)validateVisibleItems
1244 {
1245 }
1246
1247
1248 /* delegate methods */
1249
1250 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1251 itemForItemIdentifier: (NSString *)itemIdentifier
1252 willBeInsertedIntoToolbar: (BOOL)flag
1253 {
1254 /* look up NSToolbarItem by identifier and return... */
1255 return [identifierToItem objectForKey: itemIdentifier];
1256 }
1257
1258 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1259 {
1260 /* return entire set.. */
1261 return activeIdentifiers;
1262 }
1263
1264 /* for configuration palette (not yet supported) */
1265 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1266 {
1267 /* return entire set... */
1268 return activeIdentifiers;
1269 //return [identifierToItem allKeys];
1270 }
1271
1272 /* optional and unneeded */
1273 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1274 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1275 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1276
1277 @end /* EmacsToolbar */
1278
1279
1280
1281 /* ==========================================================================
1282
1283 Tooltip: class implementation
1284
1285 ========================================================================== */
1286
1287 /* Needed because NeXTstep does not provide enough control over tooltip
1288 display. */
1289 @implementation EmacsTooltip
1290
1291 - init
1292 {
1293 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1294 blue: 0.792 alpha: 0.95];
1295 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1296 NSFont *sfont = [font screenFont];
1297 int height = [sfont ascender] - [sfont descender];
1298 /*[font boundingRectForFont].size.height; */
1299 NSRect r = NSMakeRect (0, 0, 100, height+6);
1300
1301 textField = [[NSTextField alloc] initWithFrame: r];
1302 [textField setFont: font];
1303 [textField setBackgroundColor: col];
1304
1305 [textField setEditable: NO];
1306 [textField setSelectable: NO];
1307 [textField setBordered: NO];
1308 [textField setBezeled: NO];
1309 [textField setDrawsBackground: YES];
1310
1311 win = [[NSWindow alloc]
1312 initWithContentRect: [textField frame]
1313 styleMask: 0
1314 backing: NSBackingStoreBuffered
1315 defer: YES];
1316 [win setHasShadow: YES];
1317 [win setReleasedWhenClosed: NO];
1318 [win setDelegate: self];
1319 [[win contentView] addSubview: textField];
1320 /* [win setBackgroundColor: col]; */
1321 [win setOpaque: NO];
1322
1323 return self;
1324 }
1325
1326 - (void) dealloc
1327 {
1328 [win close];
1329 [win release];
1330 [textField release];
1331 [super dealloc];
1332 }
1333
1334 - (void) setText: (char *)text
1335 {
1336 NSString *str = [NSString stringWithUTF8String: text];
1337 NSRect r = [textField frame];
1338 NSSize tooltipDims;
1339
1340 [textField setStringValue: str];
1341 tooltipDims = [[textField cell] cellSize];
1342
1343 r.size.width = tooltipDims.width;
1344 r.size.height = tooltipDims.height;
1345 [textField setFrame: r];
1346 }
1347
1348 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1349 {
1350 NSRect wr = [win frame];
1351
1352 wr.origin = NSMakePoint (x, y);
1353 wr.size = [textField frame].size;
1354
1355 [win setFrame: wr display: YES];
1356 [win setLevel: NSPopUpMenuWindowLevel];
1357 [win orderFront: self];
1358 [win display];
1359 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1360 selector: @selector (hide)
1361 userInfo: nil repeats: NO];
1362 [timer retain];
1363 }
1364
1365 - (void) hide
1366 {
1367 [win close];
1368 if (timer != nil)
1369 {
1370 if ([timer isValid])
1371 [timer invalidate];
1372 [timer release];
1373 timer = nil;
1374 }
1375 }
1376
1377 - (BOOL) isActive
1378 {
1379 return timer != nil;
1380 }
1381
1382 - (NSRect) frame
1383 {
1384 return [textField frame];
1385 }
1386
1387 @end /* EmacsTooltip */
1388
1389
1390
1391 /* ==========================================================================
1392
1393 Popup Dialog: implementing functions
1394
1395 ========================================================================== */
1396
1397 struct Popdown_data
1398 {
1399 NSAutoreleasePool *pool;
1400 EmacsDialogPanel *dialog;
1401 };
1402
1403 static void
1404 pop_down_menu (void *arg)
1405 {
1406 struct Popdown_data *unwind_data = arg;
1407
1408 block_input ();
1409 if (popup_activated_flag)
1410 {
1411 EmacsDialogPanel *panel = unwind_data->dialog;
1412 popup_activated_flag = 0;
1413 [panel close];
1414 [unwind_data->pool release];
1415 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1416 }
1417
1418 xfree (unwind_data);
1419 unblock_input ();
1420 }
1421
1422
1423 Lisp_Object
1424 ns_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
1425 {
1426 id dialog;
1427 Lisp_Object window, tem, title;
1428 NSPoint p;
1429 BOOL isQ;
1430 NSAutoreleasePool *pool;
1431
1432 NSTRACE (x-popup-dialog);
1433
1434 isQ = NILP (header);
1435
1436 check_window_system (f);
1437
1438 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1439 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1440
1441 title = Fcar (contents);
1442 CHECK_STRING (title);
1443
1444 if (NILP (Fcar (Fcdr (contents))))
1445 /* No buttons specified, add an "Ok" button so users can pop down
1446 the dialog. */
1447 contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1448
1449 block_input ();
1450 pool = [[NSAutoreleasePool alloc] init];
1451 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1452 isQuestion: isQ];
1453
1454 {
1455 ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1456 struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1457
1458 unwind_data->pool = pool;
1459 unwind_data->dialog = dialog;
1460
1461 record_unwind_protect_ptr (pop_down_menu, unwind_data);
1462 popup_activated_flag = 1;
1463 tem = [dialog runDialogAt: p];
1464 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
1465 }
1466
1467 unblock_input ();
1468
1469 return tem;
1470 }
1471
1472
1473 /* ==========================================================================
1474
1475 Popup Dialog: class implementation
1476
1477 ========================================================================== */
1478
1479 @interface FlippedView : NSView
1480 {
1481 }
1482 @end
1483
1484 @implementation FlippedView
1485 - (BOOL)isFlipped
1486 {
1487 return YES;
1488 }
1489 @end
1490
1491 @implementation EmacsDialogPanel
1492
1493 #define SPACER 8.0
1494 #define ICONSIZE 64.0
1495 #define TEXTHEIGHT 20.0
1496 #define MINCELLWIDTH 90.0
1497
1498 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1499 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1500 {
1501 NSSize spacing = {SPACER, SPACER};
1502 NSRect area;
1503 id cell;
1504 NSImageView *imgView;
1505 FlippedView *contentView;
1506 NSImage *img;
1507
1508 dialog_return = Qundefined;
1509 button_values = NULL;
1510 area.origin.x = 3*SPACER;
1511 area.origin.y = 2*SPACER;
1512 area.size.width = ICONSIZE;
1513 area.size.height= ICONSIZE;
1514 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1515 [img setScalesWhenResized: YES];
1516 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1517 imgView = [[NSImageView alloc] initWithFrame: area];
1518 [imgView setImage: img];
1519 [imgView setEditable: NO];
1520 [img autorelease];
1521 [imgView autorelease];
1522
1523 aStyle = NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask;
1524 flag = YES;
1525 rows = 0;
1526 cols = 1;
1527 [super initWithContentRect: contentRect styleMask: aStyle
1528 backing: backingType defer: flag];
1529 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1530 [contentView autorelease];
1531
1532 [self setContentView: contentView];
1533
1534 [[self contentView] setAutoresizesSubviews: YES];
1535
1536 [[self contentView] addSubview: imgView];
1537 [self setTitle: @""];
1538
1539 area.origin.x += ICONSIZE+2*SPACER;
1540 /* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1541 area.size.width = 400;
1542 area.size.height= TEXTHEIGHT;
1543 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1544 [[self contentView] addSubview: command];
1545 [command setStringValue: ns_app_name];
1546 [command setDrawsBackground: NO];
1547 [command setBezeled: NO];
1548 [command setSelectable: NO];
1549 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1550
1551 /* area.origin.x = ICONSIZE+2*SPACER;
1552 area.origin.y = TEXTHEIGHT + 2*SPACER;
1553 area.size.width = 400;
1554 area.size.height= 2;
1555 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1556 [[self contentView] addSubview: tem];
1557 [tem setTitlePosition: NSNoTitle];
1558 [tem setAutoresizingMask: NSViewWidthSizable];*/
1559
1560 /* area.origin.x = ICONSIZE+2*SPACER; */
1561 area.origin.y += TEXTHEIGHT+SPACER;
1562 area.size.width = 400;
1563 area.size.height= TEXTHEIGHT;
1564 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1565 [[self contentView] addSubview: title];
1566 [title setDrawsBackground: NO];
1567 [title setBezeled: NO];
1568 [title setSelectable: NO];
1569 [title setFont: [NSFont systemFontOfSize: 11.0]];
1570
1571 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1572 [cell setBordered: NO];
1573 [cell setEnabled: NO];
1574 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1575 [cell setBezelStyle: NSRoundedBezelStyle];
1576
1577 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1578 mode: NSHighlightModeMatrix
1579 prototype: cell
1580 numberOfRows: 0
1581 numberOfColumns: 1];
1582 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1583 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1584 [matrix setIntercellSpacing: spacing];
1585 [matrix autorelease];
1586
1587 [[self contentView] addSubview: matrix];
1588 [self setOneShot: YES];
1589 [self setReleasedWhenClosed: YES];
1590 [self setHidesOnDeactivate: YES];
1591 return self;
1592 }
1593
1594
1595 - (BOOL)windowShouldClose: (id)sender
1596 {
1597 window_closed = YES;
1598 [NSApp stop:self];
1599 return NO;
1600 }
1601
1602 - (void)dealloc
1603 {
1604 xfree (button_values);
1605 [super dealloc];
1606 }
1607
1608 - (void)process_dialog: (Lisp_Object) list
1609 {
1610 Lisp_Object item, lst = list;
1611 int row = 0;
1612 int buttons = 0, btnnr = 0;
1613
1614 for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1615 {
1616 item = XCAR (list);
1617 if (XTYPE (item) == Lisp_Cons)
1618 ++buttons;
1619 }
1620
1621 if (buttons > 0)
1622 button_values = xmalloc (buttons * sizeof *button_values);
1623
1624 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1625 {
1626 item = XCAR (list);
1627 if (XTYPE (item) == Lisp_String)
1628 {
1629 [self addString: SSDATA (item) row: row++];
1630 }
1631 else if (XTYPE (item) == Lisp_Cons)
1632 {
1633 button_values[btnnr] = XCDR (item);
1634 [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1635 ++btnnr;
1636 }
1637 else if (NILP (item))
1638 {
1639 [self addSplit];
1640 row = 0;
1641 }
1642 }
1643 }
1644
1645
1646 - (void)addButton: (char *)str value: (int)tag row: (int)row
1647 {
1648 id cell;
1649
1650 if (row >= rows)
1651 {
1652 [matrix addRow];
1653 rows++;
1654 }
1655 cell = [matrix cellAtRow: row column: cols-1];
1656 [cell setTarget: self];
1657 [cell setAction: @selector (clicked: )];
1658 [cell setTitle: [NSString stringWithUTF8String: str]];
1659 [cell setTag: tag];
1660 [cell setBordered: YES];
1661 [cell setEnabled: YES];
1662 }
1663
1664
1665 - (void)addString: (char *)str row: (int)row
1666 {
1667 id cell;
1668
1669 if (row >= rows)
1670 {
1671 [matrix addRow];
1672 rows++;
1673 }
1674 cell = [matrix cellAtRow: row column: cols-1];
1675 [cell setTitle: [NSString stringWithUTF8String: str]];
1676 [cell setBordered: YES];
1677 [cell setEnabled: NO];
1678 }
1679
1680
1681 - (void)addSplit
1682 {
1683 [matrix addColumn];
1684 cols++;
1685 }
1686
1687
1688 - (void)clicked: sender
1689 {
1690 NSArray *sellist = nil;
1691 EMACS_INT seltag;
1692
1693 sellist = [sender selectedCells];
1694 if ([sellist count] < 1)
1695 return;
1696
1697 seltag = [[sellist objectAtIndex: 0] tag];
1698 dialog_return = button_values[seltag];
1699 [NSApp stop:self];
1700 }
1701
1702
1703 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1704 {
1705 Lisp_Object head;
1706 [super init];
1707
1708 if (XTYPE (contents) == Lisp_Cons)
1709 {
1710 head = Fcar (contents);
1711 [self process_dialog: Fcdr (contents)];
1712 }
1713 else
1714 head = contents;
1715
1716 if (XTYPE (head) == Lisp_String)
1717 [title setStringValue:
1718 [NSString stringWithUTF8String: SSDATA (head)]];
1719 else if (isQ == YES)
1720 [title setStringValue: @"Question"];
1721 else
1722 [title setStringValue: @"Information"];
1723
1724 {
1725 int i;
1726 NSRect r, s, t;
1727
1728 if (cols == 1 && rows > 1) /* Never told where to split */
1729 {
1730 [matrix addColumn];
1731 for (i = 0; i < rows/2; i++)
1732 {
1733 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1734 atRow: i column: 1];
1735 [matrix removeRow: (rows+1)/2];
1736 }
1737 }
1738
1739 [matrix sizeToFit];
1740 {
1741 NSSize csize = [matrix cellSize];
1742 if (csize.width < MINCELLWIDTH)
1743 {
1744 csize.width = MINCELLWIDTH;
1745 [matrix setCellSize: csize];
1746 [matrix sizeToCells];
1747 }
1748 }
1749
1750 [title sizeToFit];
1751 [command sizeToFit];
1752
1753 t = [matrix frame];
1754 r = [title frame];
1755 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1756 {
1757 t.origin.x = r.origin.x;
1758 t.size.width = r.size.width;
1759 }
1760 r = [command frame];
1761 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1762 {
1763 t.origin.x = r.origin.x;
1764 t.size.width = r.size.width;
1765 }
1766
1767 r = [self frame];
1768 s = [(NSView *)[self contentView] frame];
1769 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1770 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1771 [self setFrame: r display: NO];
1772 }
1773
1774 return self;
1775 }
1776
1777
1778
1779 - (void)timeout_handler: (NSTimer *)timedEntry
1780 {
1781 NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1782 location: NSMakePoint (0, 0)
1783 modifierFlags: 0
1784 timestamp: 0
1785 windowNumber: [[NSApp mainWindow] windowNumber]
1786 context: [NSApp context]
1787 subtype: 0
1788 data1: 0
1789 data2: 0];
1790
1791 timer_fired = YES;
1792 /* We use sto because stopModal/abortModal out of the main loop does not
1793 seem to work in 10.6. But as we use stop we must send a real event so
1794 the stop is seen and acted upon. */
1795 [NSApp stop:self];
1796 [NSApp postEvent: nxev atStart: NO];
1797 }
1798
1799 - (Lisp_Object)runDialogAt: (NSPoint)p
1800 {
1801 Lisp_Object ret = Qundefined;
1802
1803 while (popup_activated_flag)
1804 {
1805 NSTimer *tmo = nil;
1806 struct timespec next_time = timer_check ();
1807
1808 if (timespec_valid_p (next_time))
1809 {
1810 double time = timespectod (next_time);
1811 tmo = [NSTimer timerWithTimeInterval: time
1812 target: self
1813 selector: @selector (timeout_handler:)
1814 userInfo: 0
1815 repeats: NO];
1816 [[NSRunLoop currentRunLoop] addTimer: tmo
1817 forMode: NSModalPanelRunLoopMode];
1818 }
1819 timer_fired = NO;
1820 dialog_return = Qundefined;
1821 [NSApp runModalForWindow: self];
1822 ret = dialog_return;
1823 if (! timer_fired)
1824 {
1825 if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1826 break;
1827 }
1828 }
1829
1830 if (EQ (ret, Qundefined) && window_closed)
1831 /* Make close button pressed equivalent to C-g. */
1832 Fsignal (Qquit, Qnil);
1833
1834 return ret;
1835 }
1836
1837 @end
1838
1839
1840 /* ==========================================================================
1841
1842 Lisp definitions
1843
1844 ========================================================================== */
1845
1846 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1847 doc: /* Cause the NS menu to be re-calculated. */)
1848 (void)
1849 {
1850 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1851 return Qnil;
1852 }
1853
1854
1855 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1856 doc: /* Return t if a menu or popup dialog is active. */)
1857 (void)
1858 {
1859 return popup_activated () ? Qt : Qnil;
1860 }
1861
1862 /* ==========================================================================
1863
1864 Lisp interface declaration
1865
1866 ========================================================================== */
1867
1868 void
1869 syms_of_nsmenu (void)
1870 {
1871 #ifndef NS_IMPL_COCOA
1872 /* Don't know how to keep track of this in Next/Open/GNUstep. Always
1873 update menus there. */
1874 trackingMenu = 1;
1875 #endif
1876 defsubr (&Sns_reset_menu);
1877 defsubr (&Smenu_or_popup_active_p);
1878
1879 DEFSYM (Qdebug_on_next_call, "debug-on-next-call");
1880 }