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