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