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