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