Replace bcopy, bzero, bcmp by memcpy, memmove, memset, memcmp
[bpt/emacs.git] / src / nsmenu.m
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2 Copyright (C) 2007, 2008, 2009, 2010 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 memcpy (previous_items, XVECTOR (f->menu_bar_vector)->contents,
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 (XVECTOR (items)->size * sizeof (int *));
231 submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
232 submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
233 submenu_top_level_items
234 = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
235 init_menu_items ();
236 for (i = 0; i < XVECTOR (items)->size; i += 4)
237 {
238 Lisp_Object key, string, maps;
239
240 key = XVECTOR (items)->contents[i];
241 string = XVECTOR (items)->contents[i + 1];
242 maps = XVECTOR (items)->contents[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], XVECTOR (menu_items)->contents[i]))
316 if (!(STRINGP (previous_items[i])
317 && STRINGP (XVECTOR (menu_items)->contents[i])
318 && !strcmp (SDATA (previous_items[i]),
319 SDATA (XVECTOR (menu_items)->contents[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 < XVECTOR (items)->size; i += 4)
351 {
352 Lisp_Object string;
353 string = XVECTOR (items)->contents[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 = XVECTOR (items)->size / 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 < XVECTOR (items)->size; i += 4)
439 {
440 string = XVECTOR (items)->contents[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 (XVECTOR (menu_items)->contents[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 (XVECTOR (menu_items)->contents[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 (XVECTOR (menu_items)->contents[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 (XVECTOR (menu_items)->contents[i], Qquote))
833 i += 1;
834 else if (EQ (XVECTOR (menu_items)->contents[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 *) &XVECTOR (menu_items)->contents[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 BLOCK_INPUT;
1003 [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1004 FRAME_TOOLBAR_HEIGHT (f) = 0;
1005 UNBLOCK_INPUT;
1006 }
1007
1008 void
1009 update_frame_tool_bar (FRAME_PTR f)
1010 /* --------------------------------------------------------------------------
1011 Update toolbar contents
1012 -------------------------------------------------------------------------- */
1013 {
1014 int i;
1015 EmacsView *view = FRAME_NS_VIEW (f);
1016 NSWindow *window = [view window];
1017 EmacsToolbar *toolbar = [view toolbar];
1018
1019 BLOCK_INPUT;
1020 [toolbar clearActive];
1021
1022 /* update EmacsToolbar as in GtkUtils, build items list */
1023 for (i = 0; i < f->n_tool_bar_items; ++i)
1024 {
1025 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1026 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1027
1028 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1029 BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1030 int idx;
1031 int img_id;
1032 struct image *img;
1033 Lisp_Object image;
1034 Lisp_Object helpObj;
1035 char *helpText;
1036
1037 /* If image is a vector, choose the image according to the
1038 button state. */
1039 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1040 if (VECTORP (image))
1041 {
1042 /* NS toolbar auto-computes disabled and selected images */
1043 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1044 xassert (ASIZE (image) >= idx);
1045 image = AREF (image, idx);
1046 }
1047 else
1048 {
1049 idx = -1;
1050 }
1051 /* Ignore invalid image specifications. */
1052 if (!valid_image_p (image))
1053 {
1054 NSLog (@"Invalid image for toolbar item");
1055 continue;
1056 }
1057
1058 img_id = lookup_image (f, image);
1059 img = IMAGE_FROM_ID (f, img_id);
1060 prepare_image_for_display (f, img);
1061
1062 if (img->load_failed_p || img->pixmap == nil)
1063 {
1064 NSLog (@"Could not prepare toolbar image for display.");
1065 continue;
1066 }
1067
1068 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1069 if (NILP (helpObj))
1070 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1071 helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1072
1073 [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1074 enabled: enabled_p];
1075 #undef TOOLPROP
1076 }
1077
1078 if (![toolbar isVisible])
1079 [toolbar setVisible: YES];
1080
1081 if ([toolbar changed])
1082 {
1083 /* inform app that toolbar has changed */
1084 NSDictionary *dict = [toolbar configurationDictionary];
1085 NSMutableDictionary *newDict = [dict mutableCopy];
1086 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1087 NSObject *key;
1088 while ((key = [keys nextObject]) != nil)
1089 {
1090 NSObject *val = [dict objectForKey: key];
1091 if ([val isKindOfClass: [NSArray class]])
1092 {
1093 [newDict setObject:
1094 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1095 forKey: key];
1096 break;
1097 }
1098 }
1099 [toolbar setConfigurationFromDictionary: newDict];
1100 [newDict release];
1101 }
1102
1103 FRAME_TOOLBAR_HEIGHT (f) =
1104 NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1105 - FRAME_NS_TITLEBAR_HEIGHT (f);
1106 UNBLOCK_INPUT;
1107 }
1108
1109
1110 /* ==========================================================================
1111
1112 Toolbar: class implementation
1113
1114 ========================================================================== */
1115
1116 @implementation EmacsToolbar
1117
1118 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1119 {
1120 self = [super initWithIdentifier: identifier];
1121 emacsView = view;
1122 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1123 [self setSizeMode: NSToolbarSizeModeSmall];
1124 [self setDelegate: self];
1125 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1126 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1127 prevEnablement = enablement = 0L;
1128 return self;
1129 }
1130
1131 - (void)dealloc
1132 {
1133 [prevIdentifiers release];
1134 [activeIdentifiers release];
1135 [identifierToItem release];
1136 [super dealloc];
1137 }
1138
1139 - (void) clearActive
1140 {
1141 [prevIdentifiers release];
1142 prevIdentifiers = [activeIdentifiers copy];
1143 [activeIdentifiers removeAllObjects];
1144 prevEnablement = enablement;
1145 enablement = 0L;
1146 }
1147
1148 - (BOOL) changed
1149 {
1150 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1151 enablement == prevEnablement ? NO : YES;
1152 }
1153
1154 - (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1155 helpText: (char *)help enabled: (BOOL)enabled
1156 {
1157 /* 1) come up w/identifier */
1158 NSString *identifier
1159 = [NSString stringWithFormat: @"%u", [img hash]];
1160
1161 /* 2) create / reuse item */
1162 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1163 if (item == nil)
1164 {
1165 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1166 autorelease];
1167 [item setImage: img];
1168 [item setToolTip: [NSString stringWithUTF8String: help]];
1169 [item setTarget: emacsView];
1170 [item setAction: @selector (toolbarClicked:)];
1171 }
1172
1173 [item setTag: idx];
1174 [item setEnabled: enabled];
1175
1176 /* 3) update state */
1177 [identifierToItem setObject: item forKey: identifier];
1178 [activeIdentifiers addObject: identifier];
1179 enablement = (enablement << 1) | (enabled == YES);
1180 }
1181
1182 /* This overrides super's implementation, which automatically sets
1183 all items to enabled state (for some reason). */
1184 - (void)validateVisibleItems { }
1185
1186
1187 /* delegate methods */
1188
1189 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1190 itemForItemIdentifier: (NSString *)itemIdentifier
1191 willBeInsertedIntoToolbar: (BOOL)flag
1192 {
1193 /* look up NSToolbarItem by identifier and return... */
1194 return [identifierToItem objectForKey: itemIdentifier];
1195 }
1196
1197 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1198 {
1199 /* return entire set.. */
1200 return activeIdentifiers;
1201 }
1202
1203 /* for configuration palette (not yet supported) */
1204 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1205 {
1206 /* return entire set... */
1207 return [identifierToItem allKeys];
1208 }
1209
1210 /* optional and unneeded */
1211 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1212 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1213 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1214
1215 @end /* EmacsToolbar */
1216
1217
1218
1219 /* ==========================================================================
1220
1221 Tooltip: class implementation
1222
1223 ========================================================================== */
1224
1225 /* Needed because NeXTstep does not provide enough control over tooltip
1226 display. */
1227 @implementation EmacsTooltip
1228
1229 - init
1230 {
1231 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1232 blue: 0.792 alpha: 0.95];
1233 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1234 NSFont *sfont = [font screenFont];
1235 int height = [sfont ascender] - [sfont descender];
1236 /*[font boundingRectForFont].size.height; */
1237 NSRect r = NSMakeRect (0, 0, 100, height+6);
1238
1239 textField = [[NSTextField alloc] initWithFrame: r];
1240 [textField setFont: font];
1241 [textField setBackgroundColor: col];
1242
1243 [textField setEditable: NO];
1244 [textField setSelectable: NO];
1245 [textField setBordered: YES];
1246 [textField setBezeled: YES];
1247 [textField setDrawsBackground: YES];
1248
1249 win = [[NSWindow alloc]
1250 initWithContentRect: [textField frame]
1251 styleMask: 0
1252 backing: NSBackingStoreBuffered
1253 defer: YES];
1254 [win setReleasedWhenClosed: NO];
1255 [win setDelegate: self];
1256 [[win contentView] addSubview: textField];
1257 /* [win setBackgroundColor: col]; */
1258 [win setOpaque: NO];
1259
1260 return self;
1261 }
1262
1263 - (void) dealloc
1264 {
1265 [win close];
1266 [win release];
1267 [textField release];
1268 [super dealloc];
1269 }
1270
1271 - (void) setText: (char *)text
1272 {
1273 NSString *str = [NSString stringWithUTF8String: text];
1274 NSRect r = [textField frame];
1275 NSSize textSize = [str sizeWithAttributes:
1276 [NSDictionary dictionaryWithObject: [[textField font] screenFont]
1277 forKey: NSFontAttributeName]];
1278 NSSize padSize = [[[textField font] screenFont]
1279 boundingRectForFont].size;
1280
1281 r.size.width = textSize.width + padSize.width/2;
1282 r.size.height = textSize.height + padSize.height/2;
1283 [textField setFrame: r];
1284 [textField setStringValue: str];
1285 }
1286
1287 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1288 {
1289 NSRect wr = [win frame];
1290
1291 wr.origin = NSMakePoint (x, y);
1292 wr.size = [textField frame].size;
1293
1294 [win setFrame: wr display: YES];
1295 [win orderFront: self];
1296 [win display];
1297 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1298 selector: @selector (hide)
1299 userInfo: nil repeats: NO];
1300 [timer retain];
1301 }
1302
1303 - (void) hide
1304 {
1305 [win close];
1306 if (timer != nil)
1307 {
1308 if ([timer isValid])
1309 [timer invalidate];
1310 [timer release];
1311 timer = nil;
1312 }
1313 }
1314
1315 - (BOOL) isActive
1316 {
1317 return timer != nil;
1318 }
1319
1320 - (NSRect) frame
1321 {
1322 return [textField frame];
1323 }
1324
1325 @end /* EmacsTooltip */
1326
1327
1328
1329 /* ==========================================================================
1330
1331 Popup Dialog: implementing functions
1332
1333 ========================================================================== */
1334
1335
1336 static Lisp_Object
1337 pop_down_menu (Lisp_Object arg)
1338 {
1339 struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
1340 if (popup_activated_flag)
1341 {
1342 popup_activated_flag = 0;
1343 BLOCK_INPUT;
1344 [NSApp endModalSession: popupSession];
1345 [((EmacsDialogPanel *) (p->pointer)) close];
1346 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1347 UNBLOCK_INPUT;
1348 }
1349 return Qnil;
1350 }
1351
1352
1353 Lisp_Object
1354 ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1355 {
1356 id dialog;
1357 Lisp_Object window, tem;
1358 struct frame *f;
1359 NSPoint p;
1360 BOOL isQ;
1361
1362 NSTRACE (x-popup-dialog);
1363
1364 check_ns ();
1365
1366 isQ = NILP (header);
1367
1368 if (EQ (position, Qt)
1369 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
1370 || EQ (XCAR (position), Qtool_bar))))
1371 {
1372 window = selected_window;
1373 }
1374 else if (CONSP (position))
1375 {
1376 Lisp_Object tem;
1377 tem = Fcar (position);
1378 if (XTYPE (tem) == Lisp_Cons)
1379 window = Fcar (Fcdr (position));
1380 else
1381 {
1382 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1383 window = Fcar (tem); /* POSN_WINDOW (tem) */
1384 }
1385 }
1386 else if (WINDOWP (position) || FRAMEP (position))
1387 {
1388 window = position;
1389 }
1390 else
1391 window = Qnil;
1392
1393 if (FRAMEP (window))
1394 f = XFRAME (window);
1395 else if (WINDOWP (window))
1396 {
1397 CHECK_LIVE_WINDOW (window);
1398 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1399 }
1400 else
1401 CHECK_WINDOW (window);
1402
1403 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1404 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1405
1406 BLOCK_INPUT;
1407 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1408 isQuestion: isQ];
1409 {
1410 int specpdl_count = SPECPDL_INDEX ();
1411 record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
1412 popup_activated_flag = 1;
1413 tem = [dialog runDialogAt: p];
1414 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
1415 }
1416 UNBLOCK_INPUT;
1417
1418 return tem;
1419 }
1420
1421
1422 /* ==========================================================================
1423
1424 Popup Dialog: class implementation
1425
1426 ========================================================================== */
1427
1428 @interface FlippedView : NSView
1429 {
1430 }
1431 @end
1432
1433 @implementation FlippedView
1434 - (BOOL)isFlipped
1435 {
1436 return YES;
1437 }
1438 @end
1439
1440 @implementation EmacsDialogPanel
1441
1442 #define SPACER 8.0
1443 #define ICONSIZE 64.0
1444 #define TEXTHEIGHT 20.0
1445 #define MINCELLWIDTH 90.0
1446
1447 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1448 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1449 {
1450 NSSize spacing = {SPACER, SPACER};
1451 NSRect area;
1452 char this_cmd_name[80];
1453 id cell;
1454 static NSImageView *imgView;
1455 static FlippedView *contentView;
1456
1457 if (imgView == nil)
1458 {
1459 NSImage *img;
1460 area.origin.x = 3*SPACER;
1461 area.origin.y = 2*SPACER;
1462 area.size.width = ICONSIZE;
1463 area.size.height= ICONSIZE;
1464 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1465 [img setScalesWhenResized: YES];
1466 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1467 imgView = [[NSImageView alloc] initWithFrame: area];
1468 [imgView setImage: img];
1469 [imgView setEditable: NO];
1470 [img release];
1471 }
1472
1473 aStyle = NSTitledWindowMask;
1474 flag = YES;
1475 rows = 0;
1476 cols = 1;
1477 [super initWithContentRect: contentRect styleMask: aStyle
1478 backing: backingType defer: flag];
1479 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1480 [self setContentView: contentView];
1481
1482 [[self contentView] setAutoresizesSubviews: YES];
1483
1484 [[self contentView] addSubview: imgView];
1485 [self setTitle: @""];
1486
1487 area.origin.x += ICONSIZE+2*SPACER;
1488 /* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1489 area.size.width = 400;
1490 area.size.height= TEXTHEIGHT;
1491 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1492 [[self contentView] addSubview: command];
1493 [command setStringValue: ns_app_name];
1494 [command setDrawsBackground: NO];
1495 [command setBezeled: NO];
1496 [command setSelectable: NO];
1497 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1498
1499 /* area.origin.x = ICONSIZE+2*SPACER;
1500 area.origin.y = TEXTHEIGHT + 2*SPACER;
1501 area.size.width = 400;
1502 area.size.height= 2;
1503 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1504 [[self contentView] addSubview: tem];
1505 [tem setTitlePosition: NSNoTitle];
1506 [tem setAutoresizingMask: NSViewWidthSizable];*/
1507
1508 /* area.origin.x = ICONSIZE+2*SPACER; */
1509 area.origin.y += TEXTHEIGHT+SPACER;
1510 area.size.width = 400;
1511 area.size.height= TEXTHEIGHT;
1512 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1513 [[self contentView] addSubview: title];
1514 [title setDrawsBackground: NO];
1515 [title setBezeled: NO];
1516 [title setSelectable: NO];
1517 [title setFont: [NSFont systemFontOfSize: 11.0]];
1518
1519 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1520 [cell setBordered: NO];
1521 [cell setEnabled: NO];
1522 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1523 [cell setBezelStyle: NSRoundedBezelStyle];
1524
1525 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1526 mode: NSHighlightModeMatrix
1527 prototype: cell
1528 numberOfRows: 0
1529 numberOfColumns: 1];
1530 [[self contentView] addSubview: matrix];
1531 [matrix release];
1532 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1533 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1534 [matrix setIntercellSpacing: spacing];
1535
1536 [self setOneShot: YES];
1537 [self setReleasedWhenClosed: YES];
1538 [self setHidesOnDeactivate: YES];
1539 return self;
1540 }
1541
1542
1543 - (BOOL)windowShouldClose: (id)sender
1544 {
1545 [NSApp stopModalWithCode: XHASH (Qnil)]; // FIXME: BIG UGLY HACK!!
1546 return NO;
1547 }
1548
1549
1550 void process_dialog (id window, Lisp_Object list)
1551 {
1552 Lisp_Object item;
1553 int row = 0;
1554
1555 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1556 {
1557 item = XCAR (list);
1558 if (XTYPE (item) == Lisp_String)
1559 {
1560 [window addString: SDATA (item) row: row++];
1561 }
1562 else if (XTYPE (item) == Lisp_Cons)
1563 {
1564 [window addButton: SDATA (XCAR (item))
1565 value: XCDR (item) row: row++];
1566 }
1567 else if (NILP (item))
1568 {
1569 [window addSplit];
1570 row = 0;
1571 }
1572 }
1573 }
1574
1575
1576 - addButton: (char *)str value: (Lisp_Object)val row: (int)row
1577 {
1578 id cell;
1579
1580 if (row >= rows)
1581 {
1582 [matrix addRow];
1583 rows++;
1584 }
1585 cell = [matrix cellAtRow: row column: cols-1];
1586 [cell setTarget: self];
1587 [cell setAction: @selector (clicked: )];
1588 [cell setTitle: [NSString stringWithUTF8String: str]];
1589 [cell setTag: XHASH (val)]; // FIXME: BIG UGLY HACK!!
1590 [cell setBordered: YES];
1591 [cell setEnabled: YES];
1592
1593 return self;
1594 }
1595
1596
1597 - addString: (char *)str row: (int)row
1598 {
1599 id cell;
1600
1601 if (row >= rows)
1602 {
1603 [matrix addRow];
1604 rows++;
1605 }
1606 cell = [matrix cellAtRow: row column: cols-1];
1607 [cell setTitle: [NSString stringWithUTF8String: str]];
1608 [cell setBordered: YES];
1609 [cell setEnabled: NO];
1610
1611 return self;
1612 }
1613
1614
1615 - addSplit
1616 {
1617 [matrix addColumn];
1618 cols++;
1619 return self;
1620 }
1621
1622
1623 - clicked: sender
1624 {
1625 NSArray *sellist = nil;
1626 EMACS_INT seltag;
1627
1628 sellist = [sender selectedCells];
1629 if ([sellist count]<1)
1630 return self;
1631
1632 seltag = [[sellist objectAtIndex: 0] tag];
1633 if (seltag != XHASH (Qundefined)) // FIXME: BIG UGLY HACK!!
1634 [NSApp stopModalWithCode: seltag];
1635 return self;
1636 }
1637
1638
1639 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1640 {
1641 Lisp_Object head;
1642 [super init];
1643
1644 if (XTYPE (contents) == Lisp_Cons)
1645 {
1646 head = Fcar (contents);
1647 process_dialog (self, Fcdr (contents));
1648 }
1649 else
1650 head = contents;
1651
1652 if (XTYPE (head) == Lisp_String)
1653 [title setStringValue:
1654 [NSString stringWithUTF8String: SDATA (head)]];
1655 else if (isQ == YES)
1656 [title setStringValue: @"Question"];
1657 else
1658 [title setStringValue: @"Information"];
1659
1660 {
1661 int i;
1662 NSRect r, s, t;
1663
1664 if (cols == 1 && rows > 1) /* Never told where to split */
1665 {
1666 [matrix addColumn];
1667 for (i = 0; i<rows/2; i++)
1668 {
1669 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1670 atRow: i column: 1];
1671 [matrix removeRow: (rows+1)/2];
1672 }
1673 }
1674
1675 [matrix sizeToFit];
1676 {
1677 NSSize csize = [matrix cellSize];
1678 if (csize.width < MINCELLWIDTH)
1679 {
1680 csize.width = MINCELLWIDTH;
1681 [matrix setCellSize: csize];
1682 [matrix sizeToCells];
1683 }
1684 }
1685
1686 [title sizeToFit];
1687 [command sizeToFit];
1688
1689 t = [matrix frame];
1690 r = [title frame];
1691 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1692 {
1693 t.origin.x = r.origin.x;
1694 t.size.width = r.size.width;
1695 }
1696 r = [command frame];
1697 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1698 {
1699 t.origin.x = r.origin.x;
1700 t.size.width = r.size.width;
1701 }
1702
1703 r = [self frame];
1704 s = [(NSView *)[self contentView] frame];
1705 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1706 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1707 [self setFrame: r display: NO];
1708 }
1709
1710 return self;
1711 }
1712
1713
1714 - (void)dealloc
1715 {
1716 { [super dealloc]; return; };
1717 }
1718
1719
1720 - (Lisp_Object)runDialogAt: (NSPoint)p
1721 {
1722 NSInteger ret;
1723 extern EMACS_TIME timer_check (int do_it_now); /* TODO: add to a header */
1724
1725 /* initiate a session that will be ended by pop_down_menu */
1726 popupSession = [NSApp beginModalSessionForWindow: self];
1727 while (popup_activated_flag
1728 && (ret = [NSApp runModalSession: popupSession])
1729 == NSRunContinuesResponse)
1730 {
1731 /* Run this for timers.el, indep of atimers; might not return.
1732 TODO: use return value to avoid calling every iteration. */
1733 timer_check (1);
1734 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
1735 }
1736
1737 { /* FIXME: BIG UGLY HACK!!! */
1738 Lisp_Object tmp;
1739 *(EMACS_INT*)(&tmp) = ret;
1740 return tmp;
1741 }
1742 }
1743
1744 @end
1745
1746
1747 /* ==========================================================================
1748
1749 Lisp definitions
1750
1751 ========================================================================== */
1752
1753 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1754 doc: /* Cause the NS menu to be re-calculated. */)
1755 ()
1756 {
1757 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1758 return Qnil;
1759 }
1760
1761
1762 DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1763 doc: /* Pop up a dialog box and return user's selection.
1764 POSITION specifies which frame to use.
1765 This is normally a mouse button event or a window or frame.
1766 If POSITION is t, it means to use the frame the mouse is on.
1767 The dialog box appears in the middle of the specified frame.
1768
1769 CONTENTS specifies the alternatives to display in the dialog box.
1770 It is a list of the form (DIALOG ITEM1 ITEM2...).
1771 Each ITEM is a cons cell (STRING . VALUE).
1772 The return value is VALUE from the chosen item.
1773
1774 An ITEM may also be just a string--that makes a nonselectable item.
1775 An ITEM may also be nil--that means to put all preceding items
1776 on the left of the dialog box and all following items on the right.
1777 \(By default, approximately half appear on each side.)
1778
1779 If HEADER is non-nil, the frame title for the box is "Information",
1780 otherwise it is "Question".
1781
1782 If the user gets rid of the dialog box without making a valid choice,
1783 for instance using the window manager, then this produces a quit and
1784 `x-popup-dialog' does not return. */)
1785 (position, contents, header)
1786 Lisp_Object position, contents, header;
1787 {
1788 return ns_popup_dialog (position, contents, header);
1789 }
1790
1791 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1792 doc: /* Return t if a menu or popup dialog is active. */)
1793 ()
1794 {
1795 return popup_activated () ? Qt : Qnil;
1796 }
1797
1798 /* ==========================================================================
1799
1800 Lisp interface declaration
1801
1802 ========================================================================== */
1803
1804 void
1805 syms_of_nsmenu ()
1806 {
1807 defsubr (&Sx_popup_dialog);
1808 defsubr (&Sns_reset_menu);
1809 defsubr (&Smenu_or_popup_active_p);
1810
1811 Qdebug_on_next_call = intern ("debug-on-next-call");
1812 staticpro (&Qdebug_on_next_call);
1813 }
1814
1815 // arch-tag: 75773656-52e5-4c44-a398-47bd87b32619