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