* nsfns.m (ns-read-file-name): BLOCK_INPUT while showing dialog. \n \n* nsmenu.m...
[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') {
614 keyEquivModMask = 0; /* signal */
615 return [NSString stringWithUTF8String: tpos];
616 }
617 return [NSString stringWithFormat: @"%c", tpos[2]];
618 }
619
620
621 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
622 {
623 NSMenuItem *item;
624 widget_value *wv = (widget_value *)wvptr;
625
626 if (name_is_separator (wv->name))
627 {
628 item = [NSMenuItem separatorItem];
629 [self addItem: item];
630 }
631 else
632 {
633 NSString *title, *keyEq;
634 title = [NSString stringWithUTF8String: wv->name];
635 if (title == nil)
636 title = @"< ? >"; /* (get out in the open so we know about it) */
637
638 keyEq = [self parseKeyEquiv: wv->key];
639 if (keyEquivModMask == 0)
640 title = [title stringByAppendingFormat: @" (%@)", keyEq];
641
642 item = [self addItemWithTitle: (NSString *)title
643 action: @selector (menuDown:)
644 keyEquivalent: keyEq];
645 [item setKeyEquivalentModifierMask: keyEquivModMask];
646
647 [item setEnabled: wv->enabled];
648
649 /* Draw radio buttons and tickboxes */
650 if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE ||
651 wv->button_type == BUTTON_TYPE_RADIO))
652 [item setState: NSOnState];
653 else
654 [item setState: NSOffState];
655
656 [item setTag: (int)wv->call_data];
657 }
658
659 return item;
660 }
661
662
663 /* convenience */
664 -(void) clear
665 {
666 int n;
667
668 for (n = [self numberOfItems]-1; n >= 0; n--)
669 {
670 NSMenuItem *item = [self itemAtIndex: n];
671 NSString *title = [item title];
672 if (([title length] == 0 || [@"Apple" isEqualToString: title])
673 && ![item isSeparatorItem])
674 continue;
675 [self removeItemAtIndex: n];
676 }
677 }
678
679
680 - (void)fillWithWidgetValue: (void *)wvptr
681 {
682 widget_value *wv = (widget_value *)wvptr;
683
684 /* clear existing contents */
685 [self setMenuChangedMessagesEnabled: NO];
686 [self clear];
687
688 /* add new contents */
689 for (; wv != NULL; wv = wv->next)
690 {
691 NSMenuItem *item = [self addItemWithWidgetValue: wv];
692
693 if (wv->contents)
694 {
695 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
696
697 [self setSubmenu: submenu forItem: item];
698 [submenu fillWithWidgetValue: wv->contents];
699 [submenu release];
700 [item setAction: nil];
701 }
702 }
703
704 [self setMenuChangedMessagesEnabled: YES];
705 #ifdef NS_IMPL_GNUSTEP
706 if ([[self window] isVisible])
707 [self sizeToFit];
708 #else
709 if ([self supermenu] == nil)
710 [self sizeToFit];
711 #endif
712 }
713
714
715 /* adds an empty submenu and returns it */
716 - (EmacsMenu *)addSubmenuWithTitle: (char *)title forFrame: (struct frame *)f
717 {
718 NSString *titleStr = [NSString stringWithUTF8String: title];
719 NSMenuItem *item = [self addItemWithTitle: titleStr
720 action: nil /*@selector (menuDown:) */
721 keyEquivalent: @""];
722 EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f];
723 [self setSubmenu: submenu forItem: item];
724 [submenu release];
725 return submenu;
726 }
727
728 /* run a menu in popup mode */
729 - (Lisp_Object)runMenuAt: (NSPoint)p forFrame: (struct frame *)f
730 keymaps: (int)keymaps
731 {
732 EmacsView *view = FRAME_NS_VIEW (f);
733 /* p = [view convertPoint:p fromView: nil]; */
734 p.y = NSHeight ([view frame]) - p.y;
735 NSEvent *e = [[view window] currentEvent];
736 NSEvent *event = [NSEvent mouseEventWithType: NSRightMouseDown
737 location: p
738 modifierFlags: 0
739 timestamp: [e timestamp]
740 windowNumber: [[view window] windowNumber]
741 context: [e context]
742 eventNumber: 0/*[e eventNumber] */
743 clickCount: 1
744 pressure: 0];
745 long retVal;
746
747 context_menu_value = -1;
748 [NSMenu popUpContextMenu: self withEvent: event forView: view];
749 retVal = context_menu_value;
750 context_menu_value = 0;
751 return retVal > 0
752 ? find_and_return_menu_selection (f, keymaps, (void *)retVal)
753 : Qnil;
754 }
755
756 @end /* EmacsMenu */
757
758
759
760 /* ==========================================================================
761
762 Context Menu: implementing functions
763
764 ========================================================================== */
765
766 static Lisp_Object
767 cleanup_popup_menu (Lisp_Object arg)
768 {
769 discard_menu_items ();
770 return Qnil;
771 }
772
773
774 static Lisp_Object
775 ns_popup_menu (Lisp_Object position, Lisp_Object menu)
776 {
777 EmacsMenu *pmenu;
778 struct frame *f = NULL;
779 NSPoint p;
780 Lisp_Object window, x, y, tem, keymap, title;
781 struct gcpro gcpro1;
782 int specpdl_count = SPECPDL_INDEX (), specpdl_count2;
783 char *error_name = NULL;
784 int keymaps = 0;
785 widget_value *wv, *first_wv = 0;
786
787 NSTRACE (ns_popup_menu);
788
789 if (!NILP (position))
790 {
791 check_ns ();
792
793 if (EQ (position, Qt)
794 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
795 || EQ (XCAR (position), Qtool_bar))))
796 {
797 /* Use the mouse's current position. */
798 struct frame *new_f = SELECTED_FRAME ();
799
800 if (FRAME_TERMINAL (new_f)->mouse_position_hook)
801 (*FRAME_TERMINAL (new_f)->mouse_position_hook)
802 (&new_f, 0, 0, 0, &x, &y, 0);
803 if (new_f != 0)
804 XSETFRAME (window, new_f);
805 else
806 {
807 window = selected_window;
808 x = make_number (0);
809 y = make_number (0);
810 }
811 }
812 else
813 {
814 CHECK_CONS (position);
815 tem = Fcar (position);
816 if (XTYPE (tem) == Lisp_Cons)
817 {
818 window = Fcar (Fcdr (position));
819 x = Fcar (tem);
820 y = Fcar (Fcdr (tem));
821 }
822 else
823 {
824 tem = Fcar (Fcdr (position));
825 window = Fcar (tem);
826 tem = Fcar (Fcdr (Fcdr (tem)));
827 x = Fcar (tem);
828 y = Fcdr (tem);
829 }
830 }
831
832 CHECK_NUMBER (x);
833 CHECK_NUMBER (y);
834
835 if (FRAMEP (window))
836 {
837 f = XFRAME (window);
838
839 p.x = 0;
840 p.y = 0;
841 }
842 else
843 {
844 struct window *win = XWINDOW (window);
845 CHECK_LIVE_WINDOW (window);
846 f = XFRAME (WINDOW_FRAME (win));
847 p.x = FRAME_COLUMN_WIDTH (f) * WINDOW_LEFT_EDGE_COL (win);
848 p.y = FRAME_LINE_HEIGHT (f) * WINDOW_TOP_EDGE_LINE (win);
849 }
850
851 p.x += XINT (x); p.y += XINT (y);
852
853 XSETFRAME (Vmenu_updating_frame, f);
854 }
855 else
856 { /* no position given */
857 /* FIXME: if called during dump, we need to stop precomputation of
858 key equivalents (see below) because the keydefs in ns-win.el have
859 not been loaded yet. */
860 if (noninteractive)
861 return Qnil;
862 Vmenu_updating_frame = Qnil;
863 }
864
865 /* now parse the lisp menus */
866 record_unwind_protect (unuse_menu_items, Qnil);
867 title = Qnil;
868 GCPRO1 (title);
869
870 /* Decode the menu items from what was specified. */
871
872 keymap = get_keymap (menu, 0, 0);
873 if (CONSP (keymap))
874 {
875 /* We were given a keymap. Extract menu info from the keymap. */
876 Lisp_Object prompt;
877
878 /* Extract the detailed info to make one pane. */
879 keymap_panes (&menu, 1, NILP (position));
880
881 /* Search for a string appearing directly as an element of the keymap.
882 That string is the title of the menu. */
883 prompt = Fkeymap_prompt (keymap);
884 title = NILP (prompt) ? build_string ("Select") : prompt;
885
886 /* Make that be the pane title of the first pane. */
887 if (!NILP (prompt) && menu_items_n_panes >= 0)
888 XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt;
889
890 keymaps = 1;
891 }
892 else if (CONSP (menu) && KEYMAPP (XCAR (menu)))
893 {
894 /* We were given a list of keymaps. */
895 int nmaps = XFASTINT (Flength (menu));
896 Lisp_Object *maps
897 = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object));
898 int i;
899
900 title = Qnil;
901
902 /* The first keymap that has a prompt string
903 supplies the menu title. */
904 for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem))
905 {
906 Lisp_Object prompt;
907
908 maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0);
909
910 prompt = Fkeymap_prompt (keymap);
911 if (NILP (title) && !NILP (prompt))
912 title = prompt;
913 }
914
915 /* Extract the detailed info to make one pane. */
916 keymap_panes (maps, nmaps, NILP (position));
917
918 /* Make the title be the pane title of the first pane. */
919 if (!NILP (title) && menu_items_n_panes >= 0)
920 XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title;
921
922 keymaps = 1;
923 }
924 else
925 {
926 /* We were given an old-fashioned menu. */
927 title = Fcar (menu);
928 CHECK_STRING (title);
929
930 list_of_panes (Fcdr (menu));
931
932 keymaps = 0;
933 }
934
935 unbind_to (specpdl_count, Qnil);
936
937 /* If no position given, that was a signal to just precompute and cache
938 key equivalents, which was a side-effect of what we just did. */
939 if (NILP (position))
940 {
941 discard_menu_items ();
942 UNGCPRO;
943 return Qnil;
944 }
945
946 record_unwind_protect (cleanup_popup_menu, Qnil);
947 BLOCK_INPUT;
948
949 /* now parse stage 2 as in ns_update_menubar */
950 wv = xmalloc_widget_value ();
951 wv->name = "contextmenu";
952 wv->value = 0;
953 wv->enabled = 1;
954 wv->button_type = BUTTON_TYPE_NONE;
955 wv->help = Qnil;
956 first_wv = wv;
957
958 specpdl_count2 = SPECPDL_INDEX ();
959
960 #if 0
961 /* FIXME: a couple of one-line differences prevent reuse */
962 wv = digest_single_submenu (0, menu_items_used, Qnil);
963 #else
964 {
965 widget_value *save_wv = 0, *prev_wv = 0;
966 widget_value **submenu_stack
967 = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
968 /* Lisp_Object *subprefix_stack
969 = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
970 int submenu_depth = 0;
971 int first_pane = 1;
972 int i;
973
974 /* Loop over all panes and items, filling in the tree. */
975 i = 0;
976 while (i < menu_items_used)
977 {
978 if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
979 {
980 submenu_stack[submenu_depth++] = save_wv;
981 save_wv = prev_wv;
982 prev_wv = 0;
983 first_pane = 1;
984 i++;
985 }
986 else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
987 {
988 prev_wv = save_wv;
989 save_wv = submenu_stack[--submenu_depth];
990 first_pane = 0;
991 i++;
992 }
993 else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
994 && submenu_depth != 0)
995 i += MENU_ITEMS_PANE_LENGTH;
996 /* Ignore a nil in the item list.
997 It's meaningful only for dialog boxes. */
998 else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
999 i += 1;
1000 else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
1001 {
1002 /* Create a new pane. */
1003 Lisp_Object pane_name, prefix;
1004 char *pane_string;
1005
1006 pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
1007 prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
1008
1009 #ifndef HAVE_MULTILINGUAL_MENU
1010 if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
1011 {
1012 pane_name = ENCODE_MENU_STRING (pane_name);
1013 ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
1014 }
1015 #endif
1016 pane_string = (NILP (pane_name)
1017 ? "" : (char *) SDATA (pane_name));
1018 /* If there is just one top-level pane, put all its items directly
1019 under the top-level menu. */
1020 if (menu_items_n_panes == 1)
1021 pane_string = "";
1022
1023 /* If the pane has a meaningful name,
1024 make the pane a top-level menu item
1025 with its items as a submenu beneath it. */
1026 if (!keymaps && strcmp (pane_string, ""))
1027 {
1028 wv = xmalloc_widget_value ();
1029 if (save_wv)
1030 save_wv->next = wv;
1031 else
1032 first_wv->contents = wv;
1033 wv->name = pane_string;
1034 if (keymaps && !NILP (prefix))
1035 wv->name++;
1036 wv->value = 0;
1037 wv->enabled = 1;
1038 wv->button_type = BUTTON_TYPE_NONE;
1039 wv->help = Qnil;
1040 save_wv = wv;
1041 prev_wv = 0;
1042 }
1043 else if (first_pane)
1044 {
1045 save_wv = wv;
1046 prev_wv = 0;
1047 }
1048 first_pane = 0;
1049 i += MENU_ITEMS_PANE_LENGTH;
1050 }
1051 else
1052 {
1053 /* Create a new item within current pane. */
1054 Lisp_Object item_name, enable, descrip, def, type, selected, help;
1055 item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
1056 enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
1057 descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
1058 def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
1059 type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
1060 selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
1061 help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
1062
1063 #ifndef HAVE_MULTILINGUAL_MENU
1064 if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
1065 {
1066 item_name = ENCODE_MENU_STRING (item_name);
1067 ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
1068 }
1069
1070 if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
1071 {
1072 descrip = ENCODE_MENU_STRING (descrip);
1073 ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
1074 }
1075 #endif /* not HAVE_MULTILINGUAL_MENU */
1076
1077 wv = xmalloc_widget_value ();
1078 if (prev_wv)
1079 prev_wv->next = wv;
1080 else
1081 save_wv->contents = wv;
1082 wv->name = (char *) SDATA (item_name);
1083 if (!NILP (descrip))
1084 wv->key = (char *) SDATA (descrip);
1085 wv->value = 0;
1086 /* If this item has a null value,
1087 make the call_data null so that it won't display a box
1088 when the mouse is on it. */
1089 wv->call_data
1090 = !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
1091 wv->enabled = !NILP (enable);
1092
1093 if (NILP (type))
1094 wv->button_type = BUTTON_TYPE_NONE;
1095 else if (EQ (type, QCtoggle))
1096 wv->button_type = BUTTON_TYPE_TOGGLE;
1097 else if (EQ (type, QCradio))
1098 wv->button_type = BUTTON_TYPE_RADIO;
1099 else
1100 abort ();
1101
1102 wv->selected = !NILP (selected);
1103
1104 if (! STRINGP (help))
1105 help = Qnil;
1106
1107 wv->help = help;
1108
1109 prev_wv = wv;
1110
1111 i += MENU_ITEMS_ITEM_LENGTH;
1112 }
1113 }
1114 }
1115 #endif
1116
1117 if (!NILP (title))
1118 {
1119 widget_value *wv_title = xmalloc_widget_value ();
1120 widget_value *wv_sep = xmalloc_widget_value ();
1121
1122 /* Maybe replace this separator with a bitmap or owner-draw item
1123 so that it looks better. Having two separators looks odd. */
1124 wv_sep->name = "--";
1125 wv_sep->next = first_wv->contents;
1126 wv_sep->help = Qnil;
1127
1128 #ifndef HAVE_MULTILINGUAL_MENU
1129 if (STRING_MULTIBYTE (title))
1130 title = ENCODE_MENU_STRING (title);
1131 #endif
1132
1133 wv_title->name = (char *) SDATA (title);
1134 wv_title->enabled = NO;
1135 wv_title->button_type = BUTTON_TYPE_NONE;
1136 wv_title->help = Qnil;
1137 wv_title->next = wv_sep;
1138 first_wv->contents = wv_title;
1139 }
1140
1141 pmenu = [[EmacsMenu alloc] initWithTitle:
1142 [NSString stringWithUTF8String: SDATA (title)]];
1143 [pmenu fillWithWidgetValue: first_wv->contents];
1144 free_menubar_widget_value_tree (first_wv);
1145 unbind_to (specpdl_count2, Qnil);
1146
1147 popup_activated_flag = 1;
1148 tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1149 popup_activated_flag = 0;
1150 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1151
1152 UNBLOCK_INPUT;
1153 discard_menu_items ();
1154 unbind_to (specpdl_count, Qnil);
1155 UNGCPRO;
1156
1157 if (error_name) error (error_name);
1158 return tem;
1159 }
1160
1161
1162
1163
1164 /* ==========================================================================
1165
1166 Toolbar: externally-called functions
1167
1168 ========================================================================== */
1169
1170 void
1171 free_frame_tool_bar (FRAME_PTR f)
1172 /* --------------------------------------------------------------------------
1173 Under NS we just hide the toolbar until it might be needed again.
1174 -------------------------------------------------------------------------- */
1175 {
1176 [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1177 }
1178
1179 void
1180 update_frame_tool_bar (FRAME_PTR f)
1181 /* --------------------------------------------------------------------------
1182 Update toolbar contents
1183 -------------------------------------------------------------------------- */
1184 {
1185 int i;
1186 EmacsToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar];
1187
1188 [toolbar clearActive];
1189
1190 /* update EmacsToolbar as in GtkUtils, build items list */
1191 for (i = 0; i < f->n_tool_bar_items; ++i)
1192 {
1193 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1194 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1195
1196 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1197 BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1198 int idx;
1199 int img_id;
1200 struct image *img;
1201 Lisp_Object image;
1202 Lisp_Object helpObj;
1203 char *helpText;
1204
1205 /* If image is a vector, choose the image according to the
1206 button state. */
1207 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1208 if (VECTORP (image))
1209 {
1210 /* NS toolbar auto-computes disabled and selected images */
1211 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1212 xassert (ASIZE (image) >= idx);
1213 image = AREF (image, idx);
1214 }
1215 else
1216 {
1217 idx = -1;
1218 }
1219 /* Ignore invalid image specifications. */
1220 if (!valid_image_p (image))
1221 {
1222 NSLog (@"Invalid image for toolbar item");
1223 continue;
1224 }
1225
1226 img_id = lookup_image (f, image);
1227 img = IMAGE_FROM_ID (f, img_id);
1228 prepare_image_for_display (f, img);
1229
1230 if (img->load_failed_p || img->pixmap == nil)
1231 {
1232 NSLog (@"Could not prepare toolbar image for display.");
1233 continue;
1234 }
1235
1236 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1237 if (NILP (helpObj))
1238 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1239 helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1240
1241 [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1242 enabled: enabled_p];
1243 #undef TOOLPROP
1244 }
1245
1246 if (![toolbar isVisible])
1247 [toolbar setVisible: YES];
1248
1249 if ([toolbar changed])
1250 {
1251 /* inform app that toolbar has changed */
1252 NSDictionary *dict = [toolbar configurationDictionary];
1253 NSMutableDictionary *newDict = [dict mutableCopy];
1254 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1255 NSObject *key;
1256 while ((key = [keys nextObject]) != nil)
1257 {
1258 NSObject *val = [dict objectForKey: key];
1259 if ([val isKindOfClass: [NSArray class]])
1260 {
1261 [newDict setObject:
1262 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1263 forKey: key];
1264 break;
1265 }
1266 }
1267 [toolbar setConfigurationFromDictionary: newDict];
1268 [newDict release];
1269 }
1270
1271 }
1272
1273
1274 /* ==========================================================================
1275
1276 Toolbar: class implementation
1277
1278 ========================================================================== */
1279
1280 @implementation EmacsToolbar
1281
1282 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1283 {
1284 self = [super initWithIdentifier: identifier];
1285 emacsView = view;
1286 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1287 [self setSizeMode: NSToolbarSizeModeSmall];
1288 [self setDelegate: self];
1289 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1290 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1291 prevEnablement = enablement = 0L;
1292 return self;
1293 }
1294
1295 - (void)dealloc
1296 {
1297 [prevIdentifiers release];
1298 [activeIdentifiers release];
1299 [identifierToItem release];
1300 [super dealloc];
1301 }
1302
1303 - (void) clearActive
1304 {
1305 [prevIdentifiers release];
1306 prevIdentifiers = [activeIdentifiers copy];
1307 [activeIdentifiers removeAllObjects];
1308 prevEnablement = enablement;
1309 enablement = 0L;
1310 }
1311
1312 - (BOOL) changed
1313 {
1314 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1315 enablement == prevEnablement ? NO : YES;
1316 }
1317
1318 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1319 helpText: (char *)help enabled: (BOOL)enabled
1320 {
1321 /* 1) come up w/identifier */
1322 NSString *identifier
1323 = [NSString stringWithFormat: @"%u", [img hash]];
1324
1325 /* 2) create / reuse item */
1326 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1327 if (item == nil)
1328 {
1329 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1330 autorelease];
1331 [item setImage: img];
1332 [item setToolTip: [NSString stringWithCString: help]];
1333 [item setTarget: emacsView];
1334 [item setAction: @selector (toolbarClicked:)];
1335 }
1336
1337 [item setTag: idx];
1338 [item setEnabled: enabled];
1339
1340 /* 3) update state */
1341 [identifierToItem setObject: item forKey: identifier];
1342 [activeIdentifiers addObject: identifier];
1343 enablement = (enablement << 1) | (enabled == YES);
1344 }
1345
1346 /* This overrides super's implementation, which automatically sets
1347 all items to enabled state (for some reason). */
1348 - (void)validateVisibleItems { }
1349
1350
1351 /* delegate methods */
1352
1353 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1354 itemForItemIdentifier: (NSString *)itemIdentifier
1355 willBeInsertedIntoToolbar: (BOOL)flag
1356 {
1357 /* look up NSToolbarItem by identifier and return... */
1358 return [identifierToItem objectForKey: itemIdentifier];
1359 }
1360
1361 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1362 {
1363 /* return entire set.. */
1364 return activeIdentifiers;
1365 }
1366
1367 /* for configuration palette (not yet supported) */
1368 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1369 {
1370 /* return entire set... */
1371 return [identifierToItem allKeys];
1372 }
1373
1374 /* optional and unneeded */
1375 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1376 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1377 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1378
1379 @end /* EmacsToolbar */
1380
1381
1382
1383 /* ==========================================================================
1384
1385 Tooltip: class implementation
1386
1387 ========================================================================== */
1388
1389 /* Needed because NeXTstep does not provide enough control over tooltip
1390 display. */
1391 @implementation EmacsTooltip
1392
1393 - init
1394 {
1395 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1396 blue: 0.792 alpha: 0.95];
1397 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1398 NSFont *sfont = [font screenFont];
1399 int height = [sfont ascender] - [sfont descender];
1400 /*[font boundingRectForFont].size.height; */
1401 NSRect r = NSMakeRect (0, 0, 100, height+6);
1402
1403 textField = [[NSTextField alloc] initWithFrame: r];
1404 [textField setFont: font];
1405 [textField setBackgroundColor: col];
1406
1407 [textField setEditable: NO];
1408 [textField setSelectable: NO];
1409 [textField setBordered: YES];
1410 [textField setBezeled: YES];
1411 [textField setDrawsBackground: YES];
1412
1413 win = [[NSWindow alloc]
1414 initWithContentRect: [textField frame]
1415 styleMask: 0
1416 backing: NSBackingStoreBuffered
1417 defer: YES];
1418 [win setReleasedWhenClosed: NO];
1419 [win setDelegate: self];
1420 [[win contentView] addSubview: textField];
1421 /* [win setBackgroundColor: col]; */
1422 [win setOpaque: NO];
1423
1424 return self;
1425 }
1426
1427 - (void) dealloc
1428 {
1429 [win close];
1430 [win release];
1431 [textField release];
1432 [super dealloc];
1433 }
1434
1435 - (void) setText: (char *)text
1436 {
1437 NSString *str = [NSString stringWithUTF8String: text];
1438 NSRect r = [textField frame];
1439 r.size.width = [[[textField font] screenFont] widthOfString: str] + 8;
1440 [textField setFrame: r];
1441 [textField setStringValue: str];
1442 }
1443
1444 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1445 {
1446 NSRect wr = [win frame];
1447
1448 wr.origin = NSMakePoint (x, y);
1449 wr.size = [textField frame].size;
1450
1451 [win setFrame: wr display: YES];
1452 [win orderFront: self];
1453 [win display];
1454 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1455 selector: @selector (hide)
1456 userInfo: nil repeats: NO];
1457 [timer retain];
1458 }
1459
1460 - (void) hide
1461 {
1462 [win close];
1463 if (timer != nil)
1464 {
1465 if ([timer isValid])
1466 [timer invalidate];
1467 [timer release];
1468 timer = nil;
1469 }
1470 }
1471
1472 - (BOOL) isActive
1473 {
1474 return timer != nil;
1475 }
1476
1477 - (NSRect) frame
1478 {
1479 return [textField frame];
1480 }
1481
1482 @end /* EmacsTooltip */
1483
1484
1485
1486 /* ==========================================================================
1487
1488 Popup Dialog: implementing functions
1489
1490 ========================================================================== */
1491
1492
1493 static Lisp_Object
1494 pop_down_menu (Lisp_Object arg)
1495 {
1496 struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1497 if (popup_activated_flag)
1498 {
1499 popup_activated_flag = 0;
1500 BLOCK_INPUT;
1501 [NSApp endModalSession: popupSession];
1502 [((EmacsDialogPanel *) (p->pointer)) close];
1503 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1504 UNBLOCK_INPUT;
1505 }
1506 return Qnil;
1507 }
1508
1509
1510 Lisp_Object
1511 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1512 {
1513 id dialog;
1514 Lisp_Object window, tem;
1515 struct frame *f;
1516 NSPoint p;
1517 BOOL isQ;
1518
1519 NSTRACE (x-popup-dialog);
1520
1521 check_ns ();
1522
1523 isQ = NILP (header);
1524
1525 if (EQ (position, Qt)
1526 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1527 || EQ (XCAR (position), Qtool_bar))))
1528 {
1529 window = selected_window;
1530 }
1531 else if (CONSP (position))
1532 {
1533 Lisp_Object tem;
1534 tem = Fcar (position);
1535 if (XTYPE (tem) == Lisp_Cons)
1536 window = Fcar (Fcdr (position));
1537 else
1538 {
1539 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1540 window = Fcar (tem); /* POSN_WINDOW (tem) */
1541 }
1542 }
1543 else if (WINDOWP (position) || FRAMEP (position))
1544 {
1545 window = position;
1546 }
1547 else
1548 window = Qnil;
1549
1550 if (FRAMEP (window))
1551 f = XFRAME (window);
1552 else if (WINDOWP (window))
1553 {
1554 CHECK_LIVE_WINDOW (window);
1555 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1556 }
1557 else
1558 CHECK_WINDOW (window);
1559
1560 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1561 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1562
1563 BLOCK_INPUT;
1564 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1565 isQuestion: isQ];
1566 {
1567 int specpdl_count = SPECPDL_INDEX ();
1568 record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1569 popup_activated_flag = 1;
1570 tem = [dialog runDialogAt: p];
1571 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
1572 }
1573 UNBLOCK_INPUT;
1574
1575 return tem;
1576 }
1577
1578
1579 /* ==========================================================================
1580
1581 Popup Dialog: class implementation
1582
1583 ========================================================================== */
1584
1585 @interface FlippedView : NSView
1586 {
1587 }
1588 @end
1589
1590 @implementation FlippedView
1591 - (BOOL)isFlipped
1592 {
1593 return YES;
1594 }
1595 @end
1596
1597 @implementation EmacsDialogPanel
1598
1599 #define SPACER 8.0
1600 #define ICONSIZE 64.0
1601 #define TEXTHEIGHT 20.0
1602 #define MINCELLWIDTH 90.0
1603
1604 - initWithContentRect: (NSRect)contentRect styleMask: (unsigned int)aStyle
1605 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1606 {
1607 NSSize spacing = {SPACER, SPACER};
1608 NSRect area;
1609 char this_cmd_name[80];
1610 id cell;
1611 static NSImageView *imgView;
1612 static FlippedView *contentView;
1613
1614 if (imgView == nil)
1615 {
1616 NSImage *img;
1617 area.origin.x = 3*SPACER;
1618 area.origin.y = 2*SPACER;
1619 area.size.width = ICONSIZE;
1620 area.size.height= ICONSIZE;
1621 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1622 [img setScalesWhenResized: YES];
1623 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1624 imgView = [[NSImageView alloc] initWithFrame: area];
1625 [imgView setImage: img];
1626 [imgView setEditable: NO];
1627 [img release];
1628 }
1629
1630 aStyle = NSTitledWindowMask;
1631 flag = YES;
1632 rows = 0;
1633 cols = 1;
1634 [super initWithContentRect: contentRect styleMask: aStyle
1635 backing: backingType defer: flag];
1636 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1637 [self setContentView: contentView];
1638
1639 [[self contentView] setAutoresizesSubviews: YES];
1640
1641 [[self contentView] addSubview: imgView];
1642 [self setTitle: @""];
1643
1644 area.origin.x += ICONSIZE+2*SPACER;
1645 /* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1646 area.size.width = 400;
1647 area.size.height= TEXTHEIGHT;
1648 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1649 [[self contentView] addSubview: command];
1650 [command setStringValue: @"Emacs"];
1651 [command setDrawsBackground: NO];
1652 [command setBezeled: NO];
1653 [command setSelectable: NO];
1654 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1655
1656 /* area.origin.x = ICONSIZE+2*SPACER;
1657 area.origin.y = TEXTHEIGHT + 2*SPACER;
1658 area.size.width = 400;
1659 area.size.height= 2;
1660 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1661 [[self contentView] addSubview: tem];
1662 [tem setTitlePosition: NSNoTitle];
1663 [tem setAutoresizingMask: NSViewWidthSizable];*/
1664
1665 /* area.origin.x = ICONSIZE+2*SPACER; */
1666 area.origin.y += TEXTHEIGHT+SPACER;
1667 area.size.width = 400;
1668 area.size.height= TEXTHEIGHT;
1669 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1670 [[self contentView] addSubview: title];
1671 [title setDrawsBackground: NO];
1672 [title setBezeled: NO];
1673 [title setSelectable: NO];
1674 [title setFont: [NSFont systemFontOfSize: 11.0]];
1675
1676 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1677 [cell setBordered: NO];
1678 [cell setEnabled: NO];
1679 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1680 [cell setBezelStyle: NSRoundedBezelStyle];
1681
1682 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1683 mode: NSHighlightModeMatrix
1684 prototype: cell
1685 numberOfRows: 0
1686 numberOfColumns: 1];
1687 [[self contentView] addSubview: matrix];
1688 [matrix release];
1689 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1690 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1691 [matrix setIntercellSpacing: spacing];
1692
1693 [self setOneShot: YES];
1694 [self setReleasedWhenClosed: YES];
1695 [self setHidesOnDeactivate: YES];
1696 return self;
1697 }
1698
1699
1700 - (BOOL)windowShouldClose: (id)sender
1701 {
1702 [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1703 return NO;
1704 }
1705
1706
1707 void process_dialog (id window, Lisp_Object list)
1708 {
1709 Lisp_Object item;
1710 int row = 0;
1711
1712 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1713 {
1714 item = XCAR (list);
1715 if (XTYPE (item) == Lisp_String)
1716 {
1717 [window addString: XSTRING (item)->data row: row++];
1718 }
1719 else if (XTYPE (item) == Lisp_Cons)
1720 {
1721 [window addButton: XSTRING (XCAR (item))->data
1722 value: XCDR (item) row: row++];
1723 }
1724 else if (NILP (item))
1725 {
1726 [window addSplit];
1727 row = 0;
1728 }
1729 }
1730 }
1731
1732
1733 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1734 {
1735 id cell;
1736
1737 if (row >= rows)
1738 {
1739 [matrix addRow];
1740 rows++;
1741 }
1742 cell = [matrix cellAtRow: row column: cols-1];
1743 [cell setTarget: self];
1744 [cell setAction: @selector (clicked: )];
1745 [cell setTitle: [NSString stringWithUTF8String: str]];
1746 [cell setTag: XHASH (val)]; // FIXME: BIG UGLY HACK!!
1747 [cell setBordered: YES];
1748 [cell setEnabled: YES];
1749
1750 return self;
1751 }
1752
1753
1754 - addString: (char *)str row: (int)row
1755 {
1756 id cell;
1757
1758 if (row >= rows)
1759 {
1760 [matrix addRow];
1761 rows++;
1762 }
1763 cell = [matrix cellAtRow: row column: cols-1];
1764 [cell setTitle: [NSString stringWithUTF8String: str]];
1765 [cell setBordered: YES];
1766 [cell setEnabled: NO];
1767
1768 return self;
1769 }
1770
1771
1772 - addSplit
1773 {
1774 [matrix addColumn];
1775 cols++;
1776 return self;
1777 }
1778
1779
1780 - clicked: sender
1781 {
1782 NSArray *sellist = nil;
1783 EMACS_INT seltag;
1784
1785 sellist = [sender selectedCells];
1786 if ([sellist count]<1)
1787 return self;
1788
1789 seltag = [[sellist objectAtIndex: 0] tag];
1790 if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1791 [NSApp stopModalWithCode: seltag];
1792 return self;
1793 }
1794
1795
1796 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1797 {
1798 Lisp_Object head;
1799 [super init];
1800
1801 if (XTYPE (contents) == Lisp_Cons)
1802 {
1803 head = Fcar (contents);
1804 process_dialog (self, Fcdr (contents));
1805 }
1806 else
1807 head = contents;
1808
1809 if (XTYPE (head) == Lisp_String)
1810 [title setStringValue:
1811 [NSString stringWithUTF8String: XSTRING (head)->data]];
1812 else if (isQ == YES)
1813 [title setStringValue: @"Question"];
1814 else
1815 [title setStringValue: @"Information"];
1816
1817 {
1818 int i;
1819 NSRect r, s, t;
1820
1821 if (cols == 1 && rows > 1) /* Never told where to split */
1822 {
1823 [matrix addColumn];
1824 for (i = 0; i<rows/2; i++)
1825 {
1826 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1827 atRow: i column: 1];
1828 [matrix removeRow: (rows+1)/2];
1829 }
1830 }
1831
1832 [matrix sizeToFit];
1833 {
1834 NSSize csize = [matrix cellSize];
1835 if (csize.width < MINCELLWIDTH)
1836 {
1837 csize.width = MINCELLWIDTH;
1838 [matrix setCellSize: csize];
1839 [matrix sizeToCells];
1840 }
1841 }
1842
1843 [title sizeToFit];
1844 [command sizeToFit];
1845
1846 t = [matrix frame];
1847 r = [title frame];
1848 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1849 {
1850 t.origin.x = r.origin.x;
1851 t.size.width = r.size.width;
1852 }
1853 r = [command frame];
1854 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1855 {
1856 t.origin.x = r.origin.x;
1857 t.size.width = r.size.width;
1858 }
1859
1860 r = [self frame];
1861 s = [(NSView *)[self contentView] frame];
1862 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1863 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1864 [self setFrame: r display: NO];
1865 }
1866
1867 return self;
1868 }
1869
1870
1871 - (void)dealloc
1872 {
1873 { [super dealloc]; return; };
1874 }
1875
1876
1877 - (Lisp_Object)runDialogAt: (NSPoint)p
1878 {
1879 int ret;
1880
1881 /* initiate a session that will be ended by pop_down_menu */
1882 popupSession = [NSApp beginModalSessionForWindow: self];
1883 while (popup_activated_flag
1884 && (ret = [NSApp runModalSession: popupSession])
1885 == NSRunContinuesResponse)
1886 {
1887 /* Run this for timers.el, indep of atimers; might not return.
1888 TODO: use return value to avoid calling every iteration. */
1889 timer_check (1);
1890 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1891 }
1892
1893 { /* FIXME: BIG UGLY HACK!!! */
1894 Lisp_Object tmp;
1895 *(EMACS_INT*)(&tmp) = ret;
1896 return tmp;
1897 }
1898 }
1899
1900 @end
1901
1902
1903 /* ==========================================================================
1904
1905 Lisp definitions
1906
1907 ========================================================================== */
1908
1909 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1910 doc: /* Cause the NS menu to be re-calculated. */)
1911 ()
1912 {
1913 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1914 return Qnil;
1915 }
1916
1917
1918 DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
1919 doc: /* Pop up a deck-of-cards menu and return user's selection.
1920 POSITION is a position specification. This is either a mouse button event
1921 or a list ((XOFFSET YOFFSET) WINDOW)
1922 where XOFFSET and YOFFSET are positions in pixels from the top left
1923 corner of WINDOW. (WINDOW may be a window or a frame object.)
1924 This controls the position of the top left of the menu as a whole.
1925 If POSITION is t, it means to use the current mouse position.
1926
1927 MENU is a specifier for a menu. For the simplest case, MENU is a keymap.
1928 The menu items come from key bindings that have a menu string as well as
1929 a definition; actually, the \"definition\" in such a key binding looks like
1930 \(STRING . REAL-DEFINITION). To give the menu a title, put a string into
1931 the keymap as a top-level element.
1932
1933 If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.
1934 Otherwise, REAL-DEFINITION should be a valid key binding definition.
1935
1936 You can also use a list of keymaps as MENU.
1937 Then each keymap makes a separate pane.
1938
1939 When MENU is a keymap or a list of keymaps, the return value is the
1940 list of events corresponding to the user's choice. Note that
1941 `x-popup-menu' does not actually execute the command bound to that
1942 sequence of events.
1943
1944 Alternatively, you can specify a menu of multiple panes
1945 with a list of the form (TITLE PANE1 PANE2...),
1946 where each pane is a list of form (TITLE ITEM1 ITEM2...).
1947 Each ITEM is normally a cons cell (STRING . VALUE);
1948 but a string can appear as an item--that makes a nonselectable line
1949 in the menu.
1950 With this form of menu, the return value is VALUE from the chosen item. */)
1951 (position, menu)
1952 Lisp_Object position, menu;
1953 {
1954 return ns_popup_menu (position, menu);
1955 }
1956
1957
1958 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1959 doc: /* Pop up a dialog box and return user's selection.
1960 POSITION specifies which frame to use.
1961 This is normally a mouse button event or a window or frame.
1962 If POSITION is t, it means to use the frame the mouse is on.
1963 The dialog box appears in the middle of the specified frame.
1964
1965 CONTENTS specifies the alternatives to display in the dialog box.
1966 It is a list of the form (DIALOG ITEM1 ITEM2...).
1967 Each ITEM is a cons cell (STRING . VALUE).
1968 The return value is VALUE from the chosen item.
1969
1970 An ITEM may also be just a string--that makes a nonselectable item.
1971 An ITEM may also be nil--that means to put all preceding items
1972 on the left of the dialog box and all following items on the right.
1973 \(By default, approximately half appear on each side.)
1974
1975 If HEADER is non-nil, the frame title for the box is "Information",
1976 otherwise it is "Question".
1977
1978 If the user gets rid of the dialog box without making a valid choice,
1979 for instance using the window manager, then this produces a quit and
1980 `x-popup-dialog' does not return. */)
1981 (position, contents, header)
1982 Lisp_Object position, contents, header;
1983 {
1984 return ns_popup_dialog (position, contents, header);
1985 }
1986
1987 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1988 doc: /* Return t if a menu or popup dialog is active. */)
1989 ()
1990 {
1991 return popup_activated () ? Qt : Qnil;
1992 }
1993
1994 /* ==========================================================================
1995
1996 Lisp interface declaration
1997
1998 ========================================================================== */
1999
2000 void
2001 syms_of_nsmenu ()
2002 {
2003 defsubr (&Sx_popup_menu);
2004 defsubr (&Sx_popup_dialog);
2005 defsubr (&Sns_reset_menu);
2006 defsubr (&Smenu_or_popup_active_p);
2007 staticpro (&menu_items);
2008 menu_items = Qnil;
2009
2010 Qdebug_on_next_call = intern ("debug-on-next-call");
2011 staticpro (&Qdebug_on_next_call);
2012 }
2013
2014 // arch-tag: 75773656-52e5-4c44-a398-47bd87b32619