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