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