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