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