]> code.delx.au - gnu-emacs/blob - src/nsmenu.m
NextSten maximization and NSTRACE rewrite.
[gnu-emacs] / src / nsmenu.m
1 /* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
2 Copyright (C) 2007-2015 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 block_input ();
1002 view->wait_for_tool_bar = NO;
1003 [[view toolbar] setVisible: NO];
1004 FRAME_TOOLBAR_HEIGHT (f) = 0;
1005 unblock_input ();
1006 }
1007
1008 void
1009 update_frame_tool_bar (struct frame *f)
1010 /* --------------------------------------------------------------------------
1011 Update toolbar contents
1012 -------------------------------------------------------------------------- */
1013 {
1014 int i, k = 0;
1015 EmacsView *view = FRAME_NS_VIEW (f);
1016 NSWindow *window = [view window];
1017 EmacsToolbar *toolbar = [view toolbar];
1018 int oldh;
1019
1020 if (view == nil || toolbar == nil) return;
1021 block_input ();
1022
1023 oldh = FRAME_TOOLBAR_HEIGHT (f);
1024
1025 #ifdef NS_IMPL_COCOA
1026 [toolbar clearActive];
1027 #else
1028 [toolbar clearAll];
1029 #endif
1030
1031 /* update EmacsToolbar as in GtkUtils, build items list */
1032 for (i = 0; i < f->n_tool_bar_items; ++i)
1033 {
1034 #define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1035 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1036
1037 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1038 int idx;
1039 ptrdiff_t img_id;
1040 struct image *img;
1041 Lisp_Object image;
1042 Lisp_Object helpObj;
1043 const char *helpText;
1044
1045 /* Check if this is a separator. */
1046 if (EQ (TOOLPROP (TOOL_BAR_ITEM_TYPE), Qt))
1047 {
1048 /* Skip separators. Newer OSX don't show them, and on GNUstep they
1049 are wide as a button, thus overflowing the toolbar most of
1050 the time. */
1051 continue;
1052 }
1053
1054 /* If image is a vector, choose the image according to the
1055 button state. */
1056 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1057 if (VECTORP (image))
1058 {
1059 /* NS toolbar auto-computes disabled and selected images */
1060 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1061 eassert (ASIZE (image) >= idx);
1062 image = AREF (image, idx);
1063 }
1064 else
1065 {
1066 idx = -1;
1067 }
1068 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1069 if (NILP (helpObj))
1070 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1071 helpText = NILP (helpObj) ? "" : SSDATA (helpObj);
1072
1073 /* Ignore invalid image specifications. */
1074 if (!valid_image_p (image))
1075 {
1076 /* Don't log anything, GNUS makes invalid images all the time. */
1077 continue;
1078 }
1079
1080 img_id = lookup_image (f, image);
1081 img = IMAGE_FROM_ID (f, img_id);
1082 prepare_image_for_display (f, img);
1083
1084 if (img->load_failed_p || img->pixmap == nil)
1085 {
1086 NSLog (@"Could not prepare toolbar image for display.");
1087 continue;
1088 }
1089
1090 [toolbar addDisplayItemWithImage: img->pixmap
1091 idx: k++
1092 tag: i
1093 helpText: helpText
1094 enabled: enabled_p];
1095 #undef TOOLPROP
1096 }
1097
1098 if (![toolbar isVisible])
1099 [toolbar setVisible: YES];
1100
1101 #ifdef NS_IMPL_COCOA
1102 if ([toolbar changed])
1103 {
1104 /* inform app that toolbar has changed */
1105 NSDictionary *dict = [toolbar configurationDictionary];
1106 NSMutableDictionary *newDict = [dict mutableCopy];
1107 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1108 id key;
1109 while ((key = [keys nextObject]) != nil)
1110 {
1111 NSObject *val = [dict objectForKey: key];
1112 if ([val isKindOfClass: [NSArray class]])
1113 {
1114 [newDict setObject:
1115 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1116 forKey: key];
1117 break;
1118 }
1119 }
1120 [toolbar setConfigurationFromDictionary: newDict];
1121 [newDict release];
1122 }
1123 #endif
1124
1125 FRAME_TOOLBAR_HEIGHT (f) =
1126 NSHeight ([window frameRectForContentRect: NSMakeRect (0, 0, 0, 0)])
1127 - FRAME_NS_TITLEBAR_HEIGHT (f);
1128 if (FRAME_TOOLBAR_HEIGHT (f) < 0) // happens if frame is fullscreen.
1129 FRAME_TOOLBAR_HEIGHT (f) = 0;
1130
1131 if (oldh != FRAME_TOOLBAR_HEIGHT (f))
1132 [view updateFrameSize:YES];
1133 if (view->wait_for_tool_bar && FRAME_TOOLBAR_HEIGHT (f) > 0)
1134 {
1135 view->wait_for_tool_bar = NO;
1136 [view setNeedsDisplay: YES];
1137 }
1138
1139 unblock_input ();
1140 }
1141
1142
1143 /* ==========================================================================
1144
1145 Toolbar: class implementation
1146
1147 ========================================================================== */
1148
1149 @implementation EmacsToolbar
1150
1151 - initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1152 {
1153 self = [super initWithIdentifier: identifier];
1154 emacsView = view;
1155 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1156 [self setSizeMode: NSToolbarSizeModeSmall];
1157 [self setDelegate: self];
1158 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1159 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1160 prevIdentifiers = nil;
1161 prevEnablement = enablement = 0L;
1162 return self;
1163 }
1164
1165 - (void)dealloc
1166 {
1167 [prevIdentifiers release];
1168 [activeIdentifiers release];
1169 [identifierToItem release];
1170 [super dealloc];
1171 }
1172
1173 - (void) clearActive
1174 {
1175 [prevIdentifiers release];
1176 prevIdentifiers = [activeIdentifiers copy];
1177 [activeIdentifiers removeAllObjects];
1178 prevEnablement = enablement;
1179 enablement = 0L;
1180 }
1181
1182 - (void) clearAll
1183 {
1184 [self clearActive];
1185 while ([[self items] count] > 0)
1186 [self removeItemAtIndex: 0];
1187 }
1188
1189 - (BOOL) changed
1190 {
1191 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1192 enablement == prevEnablement ? NO : YES;
1193 }
1194
1195 - (void) addDisplayItemWithImage: (EmacsImage *)img
1196 idx: (int)idx
1197 tag: (int)tag
1198 helpText: (const char *)help
1199 enabled: (BOOL)enabled
1200 {
1201 /* 1) come up w/identifier */
1202 NSString *identifier
1203 = [NSString stringWithFormat: @"%lu", (unsigned long)[img hash]];
1204 [activeIdentifiers addObject: identifier];
1205
1206 /* 2) create / reuse item */
1207 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1208 if (item == nil)
1209 {
1210 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1211 autorelease];
1212 [item setImage: img];
1213 [item setToolTip: [NSString stringWithUTF8String: help]];
1214 [item setTarget: emacsView];
1215 [item setAction: @selector (toolbarClicked:)];
1216 [identifierToItem setObject: item forKey: identifier];
1217 }
1218
1219 #ifdef NS_IMPL_GNUSTEP
1220 [self insertItemWithItemIdentifier: identifier atIndex: idx];
1221 #endif
1222
1223 [item setTag: tag];
1224 [item setEnabled: enabled];
1225
1226 /* 3) update state */
1227 enablement = (enablement << 1) | (enabled == YES);
1228 }
1229
1230 /* This overrides super's implementation, which automatically sets
1231 all items to enabled state (for some reason). */
1232 - (void)validateVisibleItems
1233 {
1234 }
1235
1236
1237 /* delegate methods */
1238
1239 - (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1240 itemForItemIdentifier: (NSString *)itemIdentifier
1241 willBeInsertedIntoToolbar: (BOOL)flag
1242 {
1243 /* look up NSToolbarItem by identifier and return... */
1244 return [identifierToItem objectForKey: itemIdentifier];
1245 }
1246
1247 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1248 {
1249 /* return entire set.. */
1250 return activeIdentifiers;
1251 }
1252
1253 /* for configuration palette (not yet supported) */
1254 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1255 {
1256 /* return entire set... */
1257 return activeIdentifiers;
1258 //return [identifierToItem allKeys];
1259 }
1260
1261 /* optional and unneeded */
1262 /* - toolbarWillAddItem: (NSNotification *)notification { } */
1263 /* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1264 /* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1265
1266 @end /* EmacsToolbar */
1267
1268
1269
1270 /* ==========================================================================
1271
1272 Tooltip: class implementation
1273
1274 ========================================================================== */
1275
1276 /* Needed because NeXTstep does not provide enough control over tooltip
1277 display. */
1278 @implementation EmacsTooltip
1279
1280 - init
1281 {
1282 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1283 blue: 0.792 alpha: 0.95];
1284 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1285 NSFont *sfont = [font screenFont];
1286 int height = [sfont ascender] - [sfont descender];
1287 /*[font boundingRectForFont].size.height; */
1288 NSRect r = NSMakeRect (0, 0, 100, height+6);
1289
1290 textField = [[NSTextField alloc] initWithFrame: r];
1291 [textField setFont: font];
1292 [textField setBackgroundColor: col];
1293
1294 [textField setEditable: NO];
1295 [textField setSelectable: NO];
1296 [textField setBordered: NO];
1297 [textField setBezeled: NO];
1298 [textField setDrawsBackground: YES];
1299
1300 win = [[NSWindow alloc]
1301 initWithContentRect: [textField frame]
1302 styleMask: 0
1303 backing: NSBackingStoreBuffered
1304 defer: YES];
1305 [win setHasShadow: YES];
1306 [win setReleasedWhenClosed: NO];
1307 [win setDelegate: self];
1308 [[win contentView] addSubview: textField];
1309 /* [win setBackgroundColor: col]; */
1310 [win setOpaque: NO];
1311
1312 return self;
1313 }
1314
1315 - (void) dealloc
1316 {
1317 [win close];
1318 [win release];
1319 [textField release];
1320 [super dealloc];
1321 }
1322
1323 - (void) setText: (char *)text
1324 {
1325 NSString *str = [NSString stringWithUTF8String: text];
1326 NSRect r = [textField frame];
1327 NSSize tooltipDims;
1328
1329 [textField setStringValue: str];
1330 tooltipDims = [[textField cell] cellSize];
1331
1332 r.size.width = tooltipDims.width;
1333 r.size.height = tooltipDims.height;
1334 [textField setFrame: r];
1335 }
1336
1337 - (void) showAtX: (int)x Y: (int)y for: (int)seconds
1338 {
1339 NSRect wr = [win frame];
1340
1341 wr.origin = NSMakePoint (x, y);
1342 wr.size = [textField frame].size;
1343
1344 [win setFrame: wr display: YES];
1345 [win setLevel: NSPopUpMenuWindowLevel];
1346 [win orderFront: self];
1347 [win display];
1348 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1349 selector: @selector (hide)
1350 userInfo: nil repeats: NO];
1351 [timer retain];
1352 }
1353
1354 - (void) hide
1355 {
1356 [win close];
1357 if (timer != nil)
1358 {
1359 if ([timer isValid])
1360 [timer invalidate];
1361 [timer release];
1362 timer = nil;
1363 }
1364 }
1365
1366 - (BOOL) isActive
1367 {
1368 return timer != nil;
1369 }
1370
1371 - (NSRect) frame
1372 {
1373 return [textField frame];
1374 }
1375
1376 @end /* EmacsTooltip */
1377
1378
1379
1380 /* ==========================================================================
1381
1382 Popup Dialog: implementing functions
1383
1384 ========================================================================== */
1385
1386 struct Popdown_data
1387 {
1388 NSAutoreleasePool *pool;
1389 EmacsDialogPanel *dialog;
1390 };
1391
1392 static void
1393 pop_down_menu (void *arg)
1394 {
1395 struct Popdown_data *unwind_data = arg;
1396
1397 block_input ();
1398 if (popup_activated_flag)
1399 {
1400 EmacsDialogPanel *panel = unwind_data->dialog;
1401 popup_activated_flag = 0;
1402 [panel close];
1403 [unwind_data->pool release];
1404 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1405 }
1406
1407 xfree (unwind_data);
1408 unblock_input ();
1409 }
1410
1411
1412 Lisp_Object
1413 ns_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
1414 {
1415 id dialog;
1416 Lisp_Object tem, title;
1417 NSPoint p;
1418 BOOL isQ;
1419 NSAutoreleasePool *pool;
1420
1421 NSTRACE ("ns_popup_dialog");
1422
1423 isQ = NILP (header);
1424
1425 check_window_system (f);
1426
1427 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1428 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1429
1430 title = Fcar (contents);
1431 CHECK_STRING (title);
1432
1433 if (NILP (Fcar (Fcdr (contents))))
1434 /* No buttons specified, add an "Ok" button so users can pop down
1435 the dialog. */
1436 contents = list2 (title, Fcons (build_string ("Ok"), Qt));
1437
1438 block_input ();
1439 pool = [[NSAutoreleasePool alloc] init];
1440 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1441 isQuestion: isQ];
1442
1443 {
1444 ptrdiff_t specpdl_count = SPECPDL_INDEX ();
1445 struct Popdown_data *unwind_data = xmalloc (sizeof (*unwind_data));
1446
1447 unwind_data->pool = pool;
1448 unwind_data->dialog = dialog;
1449
1450 record_unwind_protect_ptr (pop_down_menu, unwind_data);
1451 popup_activated_flag = 1;
1452 tem = [dialog runDialogAt: p];
1453 unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
1454 }
1455
1456 unblock_input ();
1457
1458 return tem;
1459 }
1460
1461
1462 /* ==========================================================================
1463
1464 Popup Dialog: class implementation
1465
1466 ========================================================================== */
1467
1468 @interface FlippedView : NSView
1469 {
1470 }
1471 @end
1472
1473 @implementation FlippedView
1474 - (BOOL)isFlipped
1475 {
1476 return YES;
1477 }
1478 @end
1479
1480 @implementation EmacsDialogPanel
1481
1482 #define SPACER 8.0
1483 #define ICONSIZE 64.0
1484 #define TEXTHEIGHT 20.0
1485 #define MINCELLWIDTH 90.0
1486
1487 - initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
1488 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1489 {
1490 NSSize spacing = {SPACER, SPACER};
1491 NSRect area;
1492 id cell;
1493 NSImageView *imgView;
1494 FlippedView *contentView;
1495 NSImage *img;
1496
1497 dialog_return = Qundefined;
1498 button_values = NULL;
1499 area.origin.x = 3*SPACER;
1500 area.origin.y = 2*SPACER;
1501 area.size.width = ICONSIZE;
1502 area.size.height= ICONSIZE;
1503 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1504 #ifdef NS_IMPL_COCOA
1505 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
1506 [img setScalesWhenResized: YES];
1507 #endif
1508 #endif
1509 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1510 imgView = [[NSImageView alloc] initWithFrame: area];
1511 [imgView setImage: img];
1512 [imgView setEditable: NO];
1513 [img autorelease];
1514 [imgView autorelease];
1515
1516 aStyle = NSTitledWindowMask|NSClosableWindowMask|NSUtilityWindowMask;
1517 flag = YES;
1518 rows = 0;
1519 cols = 1;
1520 [super initWithContentRect: contentRect styleMask: aStyle
1521 backing: backingType defer: flag];
1522 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1523 [contentView autorelease];
1524
1525 [self setContentView: contentView];
1526
1527 [[self contentView] setAutoresizesSubviews: YES];
1528
1529 [[self contentView] addSubview: imgView];
1530 [self setTitle: @""];
1531
1532 area.origin.x += ICONSIZE+2*SPACER;
1533 /* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1534 area.size.width = 400;
1535 area.size.height= TEXTHEIGHT;
1536 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1537 [[self contentView] addSubview: command];
1538 [command setStringValue: ns_app_name];
1539 [command setDrawsBackground: NO];
1540 [command setBezeled: NO];
1541 [command setSelectable: NO];
1542 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1543
1544 /* area.origin.x = ICONSIZE+2*SPACER;
1545 area.origin.y = TEXTHEIGHT + 2*SPACER;
1546 area.size.width = 400;
1547 area.size.height= 2;
1548 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1549 [[self contentView] addSubview: tem];
1550 [tem setTitlePosition: NSNoTitle];
1551 [tem setAutoresizingMask: NSViewWidthSizable];*/
1552
1553 /* area.origin.x = ICONSIZE+2*SPACER; */
1554 area.origin.y += TEXTHEIGHT+SPACER;
1555 area.size.width = 400;
1556 area.size.height= TEXTHEIGHT;
1557 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1558 [[self contentView] addSubview: title];
1559 [title setDrawsBackground: NO];
1560 [title setBezeled: NO];
1561 [title setSelectable: NO];
1562 [title setFont: [NSFont systemFontOfSize: 11.0]];
1563
1564 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1565 [cell setBordered: NO];
1566 [cell setEnabled: NO];
1567 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1568 [cell setBezelStyle: NSRoundedBezelStyle];
1569
1570 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1571 mode: NSHighlightModeMatrix
1572 prototype: cell
1573 numberOfRows: 0
1574 numberOfColumns: 1];
1575 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1576 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1577 [matrix setIntercellSpacing: spacing];
1578 [matrix autorelease];
1579
1580 [[self contentView] addSubview: matrix];
1581 [self setOneShot: YES];
1582 [self setReleasedWhenClosed: YES];
1583 [self setHidesOnDeactivate: YES];
1584 return self;
1585 }
1586
1587
1588 - (BOOL)windowShouldClose: (id)sender
1589 {
1590 window_closed = YES;
1591 [NSApp stop:self];
1592 return NO;
1593 }
1594
1595 - (void)dealloc
1596 {
1597 xfree (button_values);
1598 [super dealloc];
1599 }
1600
1601 - (void)process_dialog: (Lisp_Object) list
1602 {
1603 Lisp_Object item, lst = list;
1604 int row = 0;
1605 int buttons = 0, btnnr = 0;
1606
1607 for (; XTYPE (lst) == Lisp_Cons; lst = XCDR (lst))
1608 {
1609 item = XCAR (list);
1610 if (XTYPE (item) == Lisp_Cons)
1611 ++buttons;
1612 }
1613
1614 if (buttons > 0)
1615 button_values = xmalloc (buttons * sizeof *button_values);
1616
1617 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1618 {
1619 item = XCAR (list);
1620 if (XTYPE (item) == Lisp_String)
1621 {
1622 [self addString: SSDATA (item) row: row++];
1623 }
1624 else if (XTYPE (item) == Lisp_Cons)
1625 {
1626 button_values[btnnr] = XCDR (item);
1627 [self addButton: SSDATA (XCAR (item)) value: btnnr row: row++];
1628 ++btnnr;
1629 }
1630 else if (NILP (item))
1631 {
1632 [self addSplit];
1633 row = 0;
1634 }
1635 }
1636 }
1637
1638
1639 - (void)addButton: (char *)str value: (int)tag row: (int)row
1640 {
1641 id cell;
1642
1643 if (row >= rows)
1644 {
1645 [matrix addRow];
1646 rows++;
1647 }
1648 cell = [matrix cellAtRow: row column: cols-1];
1649 [cell setTarget: self];
1650 [cell setAction: @selector (clicked: )];
1651 [cell setTitle: [NSString stringWithUTF8String: str]];
1652 [cell setTag: tag];
1653 [cell setBordered: YES];
1654 [cell setEnabled: YES];
1655 }
1656
1657
1658 - (void)addString: (char *)str row: (int)row
1659 {
1660 id cell;
1661
1662 if (row >= rows)
1663 {
1664 [matrix addRow];
1665 rows++;
1666 }
1667 cell = [matrix cellAtRow: row column: cols-1];
1668 [cell setTitle: [NSString stringWithUTF8String: str]];
1669 [cell setBordered: YES];
1670 [cell setEnabled: NO];
1671 }
1672
1673
1674 - (void)addSplit
1675 {
1676 [matrix addColumn];
1677 cols++;
1678 }
1679
1680
1681 - (void)clicked: sender
1682 {
1683 NSArray *sellist = nil;
1684 EMACS_INT seltag;
1685
1686 sellist = [sender selectedCells];
1687 if ([sellist count] < 1)
1688 return;
1689
1690 seltag = [[sellist objectAtIndex: 0] tag];
1691 dialog_return = button_values[seltag];
1692 [NSApp stop:self];
1693 }
1694
1695
1696 - initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1697 {
1698 Lisp_Object head;
1699 [super init];
1700
1701 if (XTYPE (contents) == Lisp_Cons)
1702 {
1703 head = Fcar (contents);
1704 [self process_dialog: Fcdr (contents)];
1705 }
1706 else
1707 head = contents;
1708
1709 if (XTYPE (head) == Lisp_String)
1710 [title setStringValue:
1711 [NSString stringWithUTF8String: SSDATA (head)]];
1712 else if (isQ == YES)
1713 [title setStringValue: @"Question"];
1714 else
1715 [title setStringValue: @"Information"];
1716
1717 {
1718 int i;
1719 NSRect r, s, t;
1720
1721 if (cols == 1 && rows > 1) /* Never told where to split */
1722 {
1723 [matrix addColumn];
1724 for (i = 0; i < rows/2; i++)
1725 {
1726 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1727 atRow: i column: 1];
1728 [matrix removeRow: (rows+1)/2];
1729 }
1730 }
1731
1732 [matrix sizeToFit];
1733 {
1734 NSSize csize = [matrix cellSize];
1735 if (csize.width < MINCELLWIDTH)
1736 {
1737 csize.width = MINCELLWIDTH;
1738 [matrix setCellSize: csize];
1739 [matrix sizeToCells];
1740 }
1741 }
1742
1743 [title sizeToFit];
1744 [command sizeToFit];
1745
1746 t = [matrix frame];
1747 r = [title frame];
1748 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1749 {
1750 t.origin.x = r.origin.x;
1751 t.size.width = r.size.width;
1752 }
1753 r = [command frame];
1754 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1755 {
1756 t.origin.x = r.origin.x;
1757 t.size.width = r.size.width;
1758 }
1759
1760 r = [self frame];
1761 s = [(NSView *)[self contentView] frame];
1762 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1763 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1764 [self setFrame: r display: NO];
1765 }
1766
1767 return self;
1768 }
1769
1770
1771
1772 - (void)timeout_handler: (NSTimer *)timedEntry
1773 {
1774 NSEvent *nxev = [NSEvent otherEventWithType: NSApplicationDefined
1775 location: NSMakePoint (0, 0)
1776 modifierFlags: 0
1777 timestamp: 0
1778 windowNumber: [[NSApp mainWindow] windowNumber]
1779 context: [NSApp context]
1780 subtype: 0
1781 data1: 0
1782 data2: 0];
1783
1784 timer_fired = YES;
1785 /* We use sto because stopModal/abortModal out of the main loop does not
1786 seem to work in 10.6. But as we use stop we must send a real event so
1787 the stop is seen and acted upon. */
1788 [NSApp stop:self];
1789 [NSApp postEvent: nxev atStart: NO];
1790 }
1791
1792 - (Lisp_Object)runDialogAt: (NSPoint)p
1793 {
1794 Lisp_Object ret = Qundefined;
1795
1796 while (popup_activated_flag)
1797 {
1798 NSTimer *tmo = nil;
1799 struct timespec next_time = timer_check ();
1800
1801 if (timespec_valid_p (next_time))
1802 {
1803 double time = timespectod (next_time);
1804 tmo = [NSTimer timerWithTimeInterval: time
1805 target: self
1806 selector: @selector (timeout_handler:)
1807 userInfo: 0
1808 repeats: NO];
1809 [[NSRunLoop currentRunLoop] addTimer: tmo
1810 forMode: NSModalPanelRunLoopMode];
1811 }
1812 timer_fired = NO;
1813 dialog_return = Qundefined;
1814 [NSApp runModalForWindow: self];
1815 ret = dialog_return;
1816 if (! timer_fired)
1817 {
1818 if (tmo != nil) [tmo invalidate]; /* Cancels timer */
1819 break;
1820 }
1821 }
1822
1823 if (EQ (ret, Qundefined) && window_closed)
1824 /* Make close button pressed equivalent to C-g. */
1825 Fsignal (Qquit, Qnil);
1826
1827 return ret;
1828 }
1829
1830 @end
1831
1832
1833 /* ==========================================================================
1834
1835 Lisp definitions
1836
1837 ========================================================================== */
1838
1839 DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1840 doc: /* Cause the NS menu to be re-calculated. */)
1841 (void)
1842 {
1843 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1844 return Qnil;
1845 }
1846
1847
1848 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
1849 doc: /* Return t if a menu or popup dialog is active. */)
1850 (void)
1851 {
1852 return popup_activated () ? Qt : Qnil;
1853 }
1854
1855 /* ==========================================================================
1856
1857 Lisp interface declaration
1858
1859 ========================================================================== */
1860
1861 void
1862 syms_of_nsmenu (void)
1863 {
1864 #ifndef NS_IMPL_COCOA
1865 /* Don't know how to keep track of this in Next/Open/GNUstep. Always
1866 update menus there. */
1867 trackingMenu = 1;
1868 #endif
1869 defsubr (&Sns_reset_menu);
1870 defsubr (&Smenu_or_popup_active_p);
1871
1872 DEFSYM (Qdebug_on_next_call, "debug-on-next-call");
1873 }