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