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