Regenerate with fixed AC_FUNC_MKTIME.
[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
6GNU Emacs is free software; you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation; either version 3, or (at your option)
9any later version.
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
17along with GNU Emacs; see the file COPYING. If not, write to
18the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
19
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
81/*23: PENDING: not currently used, but should normalize with other terms. */
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
200 /* PENDING: 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. */
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
242 /* PENDING: 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)))
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 */
301 /* PENDING: following other terms here, but seems like this should be
302 done before parse stage 2 above, since its results aren't used */
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++)
308 /* PENDING: this ALWAYS fails on Buffers menu items.. something
309 about their strings causes them to change every time, so we
310 double-check failures */
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 */
336 /* PENDING: this is not correct for single-submenu case */
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 {
404 string = XVECTOR (items)->contents[4*i+1];
405
406 if (!string)
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];
694 id <NSMenuItem> item =
695 [self addItemWithTitle: titleStr
696 action: nil /*@selector (menuDown:) */
697 keyEquivalent: @""];
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;
727 return retVal > 0 ?
728 find_and_return_menu_selection (f, keymaps, (void *)retVal) : Qnil;
729}
730
731@end /* EmacsMenu */
732
733
734
735/* ==========================================================================
736
737 Context Menu: implementing functions
738
739 ========================================================================== */
740
741static Lisp_Object
742cleanup_popup_menu (Lisp_Object arg)
743{
744 discard_menu_items ();
745 return Qnil;
746}
747
748
749static Lisp_Object
750ns_popup_menu (Lisp_Object position, Lisp_Object menu)
751{
752 EmacsMenu *pmenu;
753 struct frame *f = NULL;
754 NSPoint p;
755 Lisp_Object window, x, y, tem, keymap, title;
756 struct gcpro gcpro1;
757 int specpdl_count = SPECPDL_INDEX (), specpdl_count2;
758 char *error_name = NULL;
759 int keymaps = 0;
760 widget_value *wv, *first_wv = 0;
761
762 NSTRACE (ns_popup_menu);
763
764 if (!NILP (position))
765 {
766 check_ns ();
767
768 if (EQ (position, Qt)
769 || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
770 || EQ (XCAR (position), Qtool_bar))))
771 {
772 /* Use the mouse's current position. */
773 struct frame *new_f = SELECTED_FRAME ();
774
775 if (FRAME_TERMINAL (new_f)->mouse_position_hook)
776 (*FRAME_TERMINAL (new_f)->mouse_position_hook)
777 (&new_f, 0, 0, 0, &x, &y, 0);
778 if (new_f != 0)
779 XSETFRAME (window, new_f);
780 else
781 {
782 window = selected_window;
783 x = make_number (0);
784 y = make_number (0);
785 }
786 }
787 else
788 {
789 CHECK_CONS (position);
790 tem = Fcar (position);
791 if (XTYPE (tem) == Lisp_Cons)
792 {
793 window = Fcar (Fcdr (position));
794 x = Fcar (tem);
795 y = Fcar (Fcdr (tem));
796 }
797 else
798 {
799 tem = Fcar (Fcdr (position));
800 window = Fcar (tem);
801 tem = Fcar (Fcdr (Fcdr (tem)));
802 x = Fcar (tem);
803 y = Fcdr (tem);
804 }
805 }
806
807 CHECK_NUMBER (x);
808 CHECK_NUMBER (y);
809
810 if (FRAMEP (window))
811 {
812 f = XFRAME (window);
813
814 p.x = 0;
815 p.y = 0;
816 }
817 else
818 {
819 struct window *win = XWINDOW (window);
820 CHECK_LIVE_WINDOW (window);
821 f = XFRAME (WINDOW_FRAME (win));
822 p.x = FRAME_COLUMN_WIDTH (f) * WINDOW_LEFT_EDGE_COL (win);
823 p.y = FRAME_LINE_HEIGHT (f) * WINDOW_TOP_EDGE_LINE (win);
824 }
825
826 p.x += XINT (x); p.y += XINT (y);
827
828 XSETFRAME (Vmenu_updating_frame, f);
829 }
830 else
831 { /* no position given */
832 /* PENDING: if called during dump, we need to stop precomputation of
833 key equivalents (see below) because the keydefs in ns-win.el have
834 not been loaded yet. */
835 if (noninteractive)
836 return Qnil;
837 Vmenu_updating_frame = Qnil;
838 }
839
840 /* now parse the lisp menus */
841 record_unwind_protect (unuse_menu_items, Qnil);
842 title = Qnil;
843 GCPRO1 (title);
844
845 /* Decode the menu items from what was specified. */
846
847 keymap = get_keymap (menu, 0, 0);
848 if (CONSP (keymap))
849 {
850 /* We were given a keymap. Extract menu info from the keymap. */
851 Lisp_Object prompt;
852
853 /* Extract the detailed info to make one pane. */
854 keymap_panes (&menu, 1, NILP (position));
855
856 /* Search for a string appearing directly as an element of the keymap.
857 That string is the title of the menu. */
858 prompt = Fkeymap_prompt (keymap);
859 title = NILP (prompt) ? build_string ("Select") : prompt;
860
861 /* Make that be the pane title of the first pane. */
862 if (!NILP (prompt) && menu_items_n_panes >= 0)
863 XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt;
864
865 keymaps = 1;
866 }
867 else if (CONSP (menu) && KEYMAPP (XCAR (menu)))
868 {
869 /* We were given a list of keymaps. */
870 int nmaps = XFASTINT (Flength (menu));
871 Lisp_Object *maps
872 = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object));
873 int i;
874
875 title = Qnil;
876
877 /* The first keymap that has a prompt string
878 supplies the menu title. */
879 for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem))
880 {
881 Lisp_Object prompt;
882
883 maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0);
884
885 prompt = Fkeymap_prompt (keymap);
886 if (NILP (title) && !NILP (prompt))
887 title = prompt;
888 }
889
890 /* Extract the detailed info to make one pane. */
891 keymap_panes (maps, nmaps, NILP (position));
892
893 /* Make the title be the pane title of the first pane. */
894 if (!NILP (title) && menu_items_n_panes >= 0)
895 XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title;
896
897 keymaps = 1;
898 }
899 else
900 {
901 /* We were given an old-fashioned menu. */
902 title = Fcar (menu);
903 CHECK_STRING (title);
904
905 list_of_panes (Fcdr (menu));
906
907 keymaps = 0;
908 }
909
910 unbind_to (specpdl_count, Qnil);
911
912 /* If no position given, that was a signal to just precompute and cache
913 key equivalents, which was a side-effect of what we just did. */
914 if (NILP (position))
915 {
916 discard_menu_items ();
917 UNGCPRO;
918 return Qnil;
919 }
920
921 record_unwind_protect (cleanup_popup_menu, Qnil);
922 BLOCK_INPUT;
923
924 /* now parse stage 2 as in ns_update_menubar */
925 wv = xmalloc_widget_value ();
926 wv->name = "contextmenu";
927 wv->value = 0;
928 wv->enabled = 1;
929 wv->button_type = BUTTON_TYPE_NONE;
930 wv->help = Qnil;
931 first_wv = wv;
932
933 specpdl_count2 = SPECPDL_INDEX ();
934
935#if 0
936 /*PENDING: a couple of one-line differences prevent reuse */
937 wv = digest_single_submenu (0, menu_items_used, Qnil);
938#else
939 {
940 widget_value *save_wv = 0, *prev_wv = 0;
941 widget_value **submenu_stack
942 = (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
943/* Lisp_Object *subprefix_stack
944 = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); */
945 int submenu_depth = 0;
946 int first_pane = 1;
947 int i;
948
949 /* Loop over all panes and items, filling in the tree. */
950 i = 0;
951 while (i < menu_items_used)
952 {
953 if (EQ (XVECTOR (menu_items)->contents[i], Qnil))
954 {
955 submenu_stack[submenu_depth++] = save_wv;
956 save_wv = prev_wv;
957 prev_wv = 0;
958 first_pane = 1;
959 i++;
960 }
961 else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
962 {
963 prev_wv = save_wv;
964 save_wv = submenu_stack[--submenu_depth];
965 first_pane = 0;
966 i++;
967 }
968 else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
969 && submenu_depth != 0)
970 i += MENU_ITEMS_PANE_LENGTH;
971 /* Ignore a nil in the item list.
972 It's meaningful only for dialog boxes. */
973 else if (EQ (XVECTOR (menu_items)->contents[i], Qquote))
974 i += 1;
975 else if (EQ (XVECTOR (menu_items)->contents[i], Qt))
976 {
977 /* Create a new pane. */
978 Lisp_Object pane_name, prefix;
979 char *pane_string;
980
981 pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
982 prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
983
984#ifndef HAVE_MULTILINGUAL_MENU
985 if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
986 {
987 pane_name = ENCODE_MENU_STRING (pane_name);
988 ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
989 }
990#endif
991 pane_string = (NILP (pane_name)
992 ? "" : (char *) SDATA (pane_name));
993 /* If there is just one top-level pane, put all its items directly
994 under the top-level menu. */
995 if (menu_items_n_panes == 1)
996 pane_string = "";
997
998 /* If the pane has a meaningful name,
999 make the pane a top-level menu item
1000 with its items as a submenu beneath it. */
1001 if (!keymaps && strcmp (pane_string, ""))
1002 {
1003 wv = xmalloc_widget_value ();
1004 if (save_wv)
1005 save_wv->next = wv;
1006 else
1007 first_wv->contents = wv;
1008 wv->name = pane_string;
1009 if (keymaps && !NILP (prefix))
1010 wv->name++;
1011 wv->value = 0;
1012 wv->enabled = 1;
1013 wv->button_type = BUTTON_TYPE_NONE;
1014 wv->help = Qnil;
1015 save_wv = wv;
1016 prev_wv = 0;
1017 }
1018 else if (first_pane)
1019 {
1020 save_wv = wv;
1021 prev_wv = 0;
1022 }
1023 first_pane = 0;
1024 i += MENU_ITEMS_PANE_LENGTH;
1025 }
1026 else
1027 {
1028 /* Create a new item within current pane. */
1029 Lisp_Object item_name, enable, descrip, def, type, selected, help;
1030 item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
1031 enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
1032 descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
1033 def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
1034 type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
1035 selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
1036 help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
1037
1038#ifndef HAVE_MULTILINGUAL_MENU
1039 if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
1040 {
1041 item_name = ENCODE_MENU_STRING (item_name);
1042 ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
1043 }
1044
1045 if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
1046 {
1047 descrip = ENCODE_MENU_STRING (descrip);
1048 ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
1049 }
1050#endif /* not HAVE_MULTILINGUAL_MENU */
1051
1052 wv = xmalloc_widget_value ();
1053 if (prev_wv)
1054 prev_wv->next = wv;
1055 else
1056 save_wv->contents = wv;
1057 wv->name = (char *) SDATA (item_name);
1058 if (!NILP (descrip))
1059 wv->key = (char *) SDATA (descrip);
1060 wv->value = 0;
1061 /* If this item has a null value,
1062 make the call_data null so that it won't display a box
1063 when the mouse is on it. */
1064 wv->call_data =
1065 !NILP (def) ? (void *) &XVECTOR (menu_items)->contents[i] : 0;
1066 wv->enabled = !NILP (enable);
1067
1068 if (NILP (type))
1069 wv->button_type = BUTTON_TYPE_NONE;
1070 else if (EQ (type, QCtoggle))
1071 wv->button_type = BUTTON_TYPE_TOGGLE;
1072 else if (EQ (type, QCradio))
1073 wv->button_type = BUTTON_TYPE_RADIO;
1074 else
1075 abort ();
1076
1077 wv->selected = !NILP (selected);
1078
1079 if (! STRINGP (help))
1080 help = Qnil;
1081
1082 wv->help = help;
1083
1084 prev_wv = wv;
1085
1086 i += MENU_ITEMS_ITEM_LENGTH;
1087 }
1088 }
1089 }
1090#endif
1091
1092 if (!NILP (title))
1093 {
1094 widget_value *wv_title = xmalloc_widget_value ();
1095 widget_value *wv_sep = xmalloc_widget_value ();
1096
1097 /* Maybe replace this separator with a bitmap or owner-draw item
1098 so that it looks better. Having two separators looks odd. */
1099 wv_sep->name = "--";
1100 wv_sep->next = first_wv->contents;
1101 wv_sep->help = Qnil;
1102
1103#ifndef HAVE_MULTILINGUAL_MENU
1104 if (STRING_MULTIBYTE (title))
1105 title = ENCODE_MENU_STRING (title);
1106#endif
1107
1108 wv_title->name = (char *) SDATA (title);
1109 wv_title->enabled = NULL;
1110 wv_title->button_type = BUTTON_TYPE_NONE;
1111 wv_title->help = Qnil;
1112 wv_title->next = wv_sep;
1113 first_wv->contents = wv_title;
1114 }
1115
1116 pmenu = [[EmacsMenu alloc] initWithTitle:
1117 [NSString stringWithUTF8String: SDATA (title)]];
1118 [pmenu fillWithWidgetValue: first_wv->contents];
1119 free_menubar_widget_value_tree (first_wv);
1120 unbind_to (specpdl_count2, Qnil);
1121
1122 tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
1123 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1124
1125 UNBLOCK_INPUT;
1126 unbind_to (specpdl_count, Qnil);
1127 UNGCPRO;
1128
1129 if (error_name) error (error_name);
1130 return tem;
1131}
1132
1133
1134
1135
1136/* ==========================================================================
1137
1138 Toolbar: externally-called functions
1139
1140 ========================================================================== */
1141
1142void
1143free_frame_tool_bar (FRAME_PTR f)
1144/* --------------------------------------------------------------------------
1145 Under NS we just hide the toolbar until it might be needed again.
1146 -------------------------------------------------------------------------- */
1147{
1148 [[FRAME_NS_VIEW (f) toolbar] setVisible: NO];
1149}
1150
1151void
1152update_frame_tool_bar (FRAME_PTR f)
1153/* --------------------------------------------------------------------------
1154 Update toolbar contents
1155 -------------------------------------------------------------------------- */
1156{
1157 int i;
1158 EmacsToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar];
1159
1160 if (NILP (f->tool_bar_lines) || !INTEGERP (f->tool_bar_lines))
1161 return;
1162
1163 [toolbar clearActive];
1164
1165 /* update EmacsToolbar as in GtkUtils, build items list */
1166 for (i = 0; i < f->n_tool_bar_items; ++i)
1167 {
1168#define TOOLPROP(IDX) AREF (f->tool_bar_items, \
1169 i * TOOL_BAR_ITEM_NSLOTS + (IDX))
1170
1171 BOOL enabled_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_ENABLED_P));
1172 BOOL selected_p = !NILP (TOOLPROP (TOOL_BAR_ITEM_SELECTED_P));
1173 int idx;
1174 int img_id;
1175 struct image *img;
1176 Lisp_Object image;
1177 Lisp_Object helpObj;
1178 char *helpText;
1179
1180 /* If image is a vector, choose the image according to the
1181 button state. */
1182 image = TOOLPROP (TOOL_BAR_ITEM_IMAGES);
1183 if (VECTORP (image))
1184 {
1185 /* NS toolbar auto-computes disabled and selected images */
1186 idx = TOOL_BAR_IMAGE_ENABLED_SELECTED;
1187 xassert (ASIZE (image) >= idx);
1188 image = AREF (image, idx);
1189 }
1190 else
1191 {
1192 idx = -1;
1193 }
1194 /* Ignore invalid image specifications. */
1195 if (!valid_image_p (image))
1196 {
1197 NSLog (@"Invalid image for toolbar item");
1198 continue;
1199 }
1200
1201 img_id = lookup_image (f, image);
1202 img = IMAGE_FROM_ID (f, img_id);
1203 prepare_image_for_display (f, img);
1204
1205 if (img->load_failed_p || img->pixmap == nil)
1206 {
1207 NSLog (@"Could not prepare toolbar image for display.");
1208 continue;
1209 }
1210
1211 helpObj = TOOLPROP (TOOL_BAR_ITEM_HELP);
1212 if (NILP (helpObj))
1213 helpObj = TOOLPROP (TOOL_BAR_ITEM_CAPTION);
1214 helpText = NILP (helpObj) ? "" : (char *)SDATA (helpObj);
1215
1216 [toolbar addDisplayItemWithImage: img->pixmap idx: i helpText: helpText
1217 enabled: enabled_p];
1218#undef TOOLPROP
1219 }
1220
1221 if (![toolbar isVisible])
1222 [toolbar setVisible: YES];
1223
1224 if ([toolbar changed])
1225 {
1226 /* inform app that toolbar has changed */
1227 NSDictionary *dict = [toolbar configurationDictionary];
1228 NSMutableDictionary *newDict = [dict mutableCopy];
1229 NSEnumerator *keys = [[dict allKeys] objectEnumerator];
1230 NSObject *key;
1231 while ((key = [keys nextObject]) != nil)
1232 {
1233 NSObject *val = [dict objectForKey: key];
1234 if ([val isKindOfClass: [NSArray class]])
1235 {
1236 [newDict setObject:
1237 [toolbar toolbarDefaultItemIdentifiers: toolbar]
1238 forKey: key];
1239 break;
1240 }
1241 }
1242 [toolbar setConfigurationFromDictionary: newDict];
1243 [newDict release];
1244 }
1245
1246}
1247
1248
1249/* ==========================================================================
1250
1251 Toolbar: class implementation
1252
1253 ========================================================================== */
1254
1255@implementation EmacsToolbar
1256
1257- initForView: (EmacsView *)view withIdentifier: (NSString *)identifier
1258{
1259 self = [super initWithIdentifier: identifier];
1260 emacsView = view;
1261 [self setDisplayMode: NSToolbarDisplayModeIconOnly];
1262 [self setSizeMode: NSToolbarSizeModeSmall];
1263 [self setDelegate: self];
1264 identifierToItem = [[NSMutableDictionary alloc] initWithCapacity: 10];
1265 activeIdentifiers = [[NSMutableArray alloc] initWithCapacity: 8];
1266 prevEnablement = enablement = 0L;
1267 return self;
1268}
1269
1270- (void)dealloc
1271{
1272 [prevIdentifiers release];
1273 [activeIdentifiers release];
1274 [identifierToItem release];
1275 [super dealloc];
1276}
1277
1278- (void) clearActive
1279{
1280 [prevIdentifiers release];
1281 prevIdentifiers = [activeIdentifiers copy];
1282 [activeIdentifiers removeAllObjects];
1283 prevEnablement = enablement;
1284 enablement = 0L;
1285}
1286
1287- (BOOL) changed
1288{
1289 return [activeIdentifiers isEqualToArray: prevIdentifiers] &&
1290 enablement == prevEnablement ? NO : YES;
1291}
1292
1293- (void) addDisplayItemWithImage: (EmacsImage *)img idx: (int)idx
1294 helpText: (char *)help enabled: (BOOL)enabled
1295{
1296 /* 1) come up w/identifier */
1297 NSString *identifier =
1298 [NSString stringWithFormat: @"%u", [img hash]];
1299
1300 /* 2) create / reuse item */
1301 NSToolbarItem *item = [identifierToItem objectForKey: identifier];
1302 if (item == nil)
1303 {
1304 item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
1305 autorelease];
1306 [item setImage: img];
1307 [item setToolTip: [NSString stringWithCString: help]];
1308 [item setTarget: emacsView];
1309 [item setAction: @selector (toolbarClicked:)];
1310 }
1311
1312 [item setTag: idx];
1313 [item setEnabled: enabled];
1314
1315 /* 3) update state */
1316 [identifierToItem setObject: item forKey: identifier];
1317 [activeIdentifiers addObject: identifier];
1318 enablement = (enablement << 1) | (enabled == YES);
1319}
1320
1321/* This overrides super's implementation, which automatically sets
1322 all items to enabled state (for some reason). */
1323- (void)validateVisibleItems { }
1324
1325
1326/* delegate methods */
1327
1328- (NSToolbarItem *)toolbar: (NSToolbar *)toolbar
1329 itemForItemIdentifier: (NSString *)itemIdentifier
1330 willBeInsertedIntoToolbar: (BOOL)flag
1331{
1332 /* look up NSToolbarItem by identifier and return... */
1333 return [identifierToItem objectForKey: itemIdentifier];
1334}
1335
1336- (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
1337{
1338 /* return entire set.. */
1339 return activeIdentifiers;
1340}
1341
1342/* for configuration palette (not yet supported) */
1343- (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
1344{
1345 /* return entire set... */
1346 return [identifierToItem allKeys];
1347}
1348
1349/* optional and unneeded */
1350/* - toolbarWillAddItem: (NSNotification *)notification { } */
1351/* - toolbarDidRemoveItem: (NSNotification *)notification { } */
1352/* - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar */
1353
1354@end /* EmacsToolbar */
1355
1356
1357
1358/* ==========================================================================
1359
1360 Tooltip: class implementation
1361
1362 ========================================================================== */
1363
1364/* Needed because NeXTstep does not provide enough control over tooltip
1365 display. */
1366@implementation EmacsTooltip
1367
1368- init
1369{
1370 NSColor *col = [NSColor colorWithCalibratedRed: 1.0 green: 1.0
1371 blue: 0.792 alpha: 0.95];
1372 NSFont *font = [NSFont toolTipsFontOfSize: 0];
1373 NSFont *sfont = [font screenFont];
1374 int height = [sfont ascender] - [sfont descender];
1375/*[font boundingRectForFont].size.height; */
1376 NSRect r = NSMakeRect (0, 0, 100, height+6);
1377
1378 textField = [[NSTextField alloc] initWithFrame: r];
1379 [textField setFont: font];
1380 [textField setBackgroundColor: col];
1381
1382 [textField setEditable: NO];
1383 [textField setSelectable: NO];
1384 [textField setBordered: YES];
1385 [textField setBezeled: YES];
1386 [textField setDrawsBackground: YES];
1387
1388 win = [[NSWindow alloc]
1389 initWithContentRect: [textField frame]
1390 styleMask: 0
1391 backing: NSBackingStoreBuffered
1392 defer: YES];
1393 [win setReleasedWhenClosed: NO];
1394 [win setDelegate: self];
1395 [[win contentView] addSubview: textField];
1396/* [win setBackgroundColor: col]; */
1397 [win setOpaque: NO];
1398
1399 return self;
1400}
1401
1402- (void) dealloc
1403{
1404 [win close];
1405 [win release];
1406 [textField release];
1407 [super dealloc];
1408}
1409
1410- (void) setText: (char *)text
1411{
1412 NSString *str = [NSString stringWithUTF8String: text];
1413 NSRect r = [textField frame];
1414 r.size.width = [[[textField font] screenFont] widthOfString: str] + 8;
1415 [textField setFrame: r];
1416 [textField setStringValue: str];
1417}
1418
1419- (void) showAtX: (int)x Y: (int)y for: (int)seconds
1420{
1421 NSRect wr = [win frame];
1422
1423 wr.origin = NSMakePoint (x, y);
1424 wr.size = [textField frame].size;
1425
1426 [win setFrame: wr display: YES];
1427 [win orderFront: self];
1428 [win display];
1429 timer = [NSTimer scheduledTimerWithTimeInterval: (float)seconds target: self
1430 selector: @selector (hide)
1431 userInfo: nil repeats: NO];
1432 [timer retain];
1433}
1434
1435- (void) hide
1436{
1437 [win close];
1438 if (timer != nil)
1439 {
1440 if ([timer isValid])
1441 [timer invalidate];
1442 [timer release];
1443 timer = nil;
1444 }
1445}
1446
1447- (BOOL) isActive
1448{
1449 return timer != nil;
1450}
1451
1452- (NSRect) frame
1453{
1454 return [textField frame];
1455}
1456
1457@end /* EmacsTooltip */
1458
1459
1460
1461/* ==========================================================================
1462
1463 Popup Dialog: implementing functions
1464
1465 ========================================================================== */
1466
1467Lisp_Object
1468ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
1469{
1470 id dialog;
1471 Lisp_Object window, tem;
1472 struct frame *f;
1473 NSPoint p;
1474 BOOL isQ;
1475
1476 NSTRACE (x-popup-dialog);
1477
1478 check_ns ();
1479
1480 isQ = NILP (header);
1481
1482 if (EQ (position, Qt))
1483 {
1484 window = selected_window;
1485 }
1486 else if (CONSP (position))
1487 {
1488 Lisp_Object tem;
1489 tem = Fcar (position);
1490 if (XTYPE (tem) == Lisp_Cons)
1491 window = Fcar (Fcdr (position));
1492 else
1493 {
1494 tem = Fcar (Fcdr (position)); /* EVENT_START (position) */
1495 window = Fcar (tem); /* POSN_WINDOW (tem) */
1496 }
1497 }
1498 else if (FRAMEP (position))
1499 {
1500 window = position;
1501 }
1502 else
1503 {
1504 CHECK_LIVE_WINDOW (position);
1505 window = position;
1506 }
1507
1508 if (FRAMEP (window))
1509 f = XFRAME (window);
1510 else
1511 {
1512 CHECK_LIVE_WINDOW (window);
1513 f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
1514 }
1515 p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
1516 p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
1517 dialog = [[EmacsDialogPanel alloc] initFromContents: contents
1518 isQuestion: isQ];
1519
1520 tem = [dialog runDialogAt: p];
1521
1522 [dialog close];
1523
1524 [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
1525 return tem;
1526}
1527
1528
1529/* ==========================================================================
1530
1531 Popup Dialog: class implementation
1532
1533 ========================================================================== */
1534
1535@interface FlippedView : NSView
1536{
1537}
1538@end
1539
1540@implementation FlippedView
1541- (BOOL)isFlipped
1542{
1543 return YES;
1544}
1545@end
1546
1547@implementation EmacsDialogPanel
1548
1549#define SPACER 8.0
1550#define ICONSIZE 64.0
1551#define TEXTHEIGHT 20.0
1552#define MINCELLWIDTH 90.0
1553
1554- initWithContentRect: (NSRect)contentRect styleMask: (unsigned int)aStyle
1555 backing: (NSBackingStoreType)backingType defer: (BOOL)flag
1556{
1557 NSSize spacing = {SPACER, SPACER};
1558 NSRect area;
1559 char this_cmd_name[80];
1560 id cell, tem;
1561 static NSImageView *imgView;
1562 static FlippedView *contentView;
1563
1564 if (imgView == nil)
1565 {
1566 NSImage *img;
1567 area.origin.x = 3*SPACER;
1568 area.origin.y = 2*SPACER;
1569 area.size.width = ICONSIZE;
1570 area.size.height= ICONSIZE;
1571 img = [[NSImage imageNamed: @"NSApplicationIcon"] copy];
1572 [img setScalesWhenResized: YES];
1573 [img setSize: NSMakeSize (ICONSIZE, ICONSIZE)];
1574 imgView = [[NSImageView alloc] initWithFrame: area];
1575 [imgView setImage: img];
1576 [imgView setEditable: NO];
1577 [img release];
1578 }
1579
1580 aStyle = NSTitledWindowMask;
1581 flag = YES;
1582 rows = 0;
1583 cols = 1;
1584 [super initWithContentRect: contentRect styleMask: aStyle
1585 backing: backingType defer: flag];
1586 contentView = [[FlippedView alloc] initWithFrame: [[self contentView] frame]];
1587 [self setContentView: contentView];
1588
1589 [[self contentView] setAutoresizesSubviews: YES];
1590
1591 [[self contentView] addSubview: imgView];
1592 [self setTitle: @""];
1593
1594 area.origin.x += ICONSIZE+2*SPACER;
1595/* area.origin.y = TEXTHEIGHT; ICONSIZE/2-10+SPACER; */
1596 area.size.width = 400;
1597 area.size.height= TEXTHEIGHT;
1598 command = [[[NSTextField alloc] initWithFrame: area] autorelease];
1599 [[self contentView] addSubview: command];
1600 [command setStringValue: @"Emacs"];
1601 [command setDrawsBackground: NO];
1602 [command setBezeled: NO];
1603 [command setSelectable: NO];
1604 [command setFont: [NSFont boldSystemFontOfSize: 13.0]];
1605
1606/* area.origin.x = ICONSIZE+2*SPACER;
1607 area.origin.y = TEXTHEIGHT + 2*SPACER;
1608 area.size.width = 400;
1609 area.size.height= 2;
1610 tem = [[[NSBox alloc] initWithFrame: area] autorelease];
1611 [[self contentView] addSubview: tem];
1612 [tem setTitlePosition: NSNoTitle];
1613 [tem setAutoresizingMask: NSViewWidthSizable];*/
1614
1615/* area.origin.x = ICONSIZE+2*SPACER; */
1616 area.origin.y += TEXTHEIGHT+SPACER;
1617 area.size.width = 400;
1618 area.size.height= TEXTHEIGHT;
1619 title = [[[NSTextField alloc] initWithFrame: area] autorelease];
1620 [[self contentView] addSubview: title];
1621 [title setDrawsBackground: NO];
1622 [title setBezeled: NO];
1623 [title setSelectable: NO];
1624 [title setFont: [NSFont systemFontOfSize: 11.0]];
1625
1626 cell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
1627 [cell setBordered: NO];
1628 [cell setEnabled: NO];
1629 [cell setCellAttribute: NSCellIsInsetButton to: 8];
1630 [cell setBezelStyle: NSRoundedBezelStyle];
1631
1632 matrix = [[NSMatrix alloc] initWithFrame: contentRect
1633 mode: NSHighlightModeMatrix
1634 prototype: cell
1635 numberOfRows: 0
1636 numberOfColumns: 1];
1637 [[self contentView] addSubview: matrix];
1638 [matrix release];
1639 [matrix setFrameOrigin: NSMakePoint (area.origin.x,
1640 area.origin.y + (TEXTHEIGHT+3*SPACER))];
1641 [matrix setIntercellSpacing: spacing];
1642
1643 [self setOneShot: YES];
1644 [self setReleasedWhenClosed: YES];
1645 [self setHidesOnDeactivate: YES];
1646 return self;
1647}
1648
1649
1650- (BOOL)windowShouldClose: (id)sender
1651{
1652 [NSApp stopModalWithCode: Qnil];
1653 return NO;
1654}
1655
1656
1657void process_dialog (id window, Lisp_Object list)
1658{
1659 Lisp_Object item;
1660 int row = 0;
1661
1662 for (; XTYPE (list) == Lisp_Cons; list = XCDR (list))
1663 {
1664 item = XCAR (list);
1665 if (XTYPE (item) == Lisp_String)
1666 {
1667 [window addString: XSTRING (item)->data row: row++];
1668 }
1669 else if (XTYPE (item) == Lisp_Cons)
1670 {
1671 [window addButton: XSTRING (XCAR (item))->data
1672 value: XCDR (item) row: row++];
1673 }
1674 else if (NILP (item))
1675 {
1676 [window addSplit];
1677 row = 0;
1678 }
1679 }
1680}
1681
1682
1683- addButton: (char *)str value: (Lisp_Object)val row: (int)row
1684{
1685 id cell;
1686
1687 if (row >= rows)
1688 {
1689 [matrix addRow];
1690 rows++;
1691 }
1692 cell = [matrix cellAtRow: row column: cols-1];
1693 [cell setTarget: self];
1694 [cell setAction: @selector (clicked: )];
1695 [cell setTitle: [NSString stringWithUTF8String: str]];
1696 [cell setTag: (int)val];
1697 [cell setBordered: YES];
1698 [cell setEnabled: YES];
1699
1700 return self;
1701}
1702
1703
1704- addString: (char *)str row: (int)row
1705{
1706 id cell;
1707
1708 if (row >= rows)
1709 {
1710 [matrix addRow];
1711 rows++;
1712 }
1713 cell = [matrix cellAtRow: row column: cols-1];
1714 [cell setTitle: [NSString stringWithUTF8String: str]];
1715 [cell setBordered: YES];
1716 [cell setEnabled: NO];
1717
1718 return self;
1719}
1720
1721
1722- addSplit
1723{
1724 [matrix addColumn];
1725 cols++;
1726 return self;
1727}
1728
1729
1730- clicked: sender
1731{
1732 NSArray *sellist = nil;
1733 Lisp_Object seltag;
1734
1735 sellist = [sender selectedCells];
1736 if ([sellist count]<1)
1737 return self;
1738
1739 seltag = (Lisp_Object)[[sellist objectAtIndex: 0] tag];
1740 if (! EQ (seltag, Qundefined))
1741 [NSApp stopModalWithCode: seltag];
1742 return self;
1743}
1744
1745
1746- initFromContents: (Lisp_Object)contents isQuestion: (BOOL)isQ
1747{
1748 Lisp_Object head;
1749 [super init];
1750
1751 if (XTYPE (contents) == Lisp_Cons)
1752 {
1753 head = Fcar (contents);
1754 process_dialog (self, Fcdr (contents));
1755 }
1756 else
1757 head = contents;
1758
1759 if (XTYPE (head) == Lisp_String)
1760 [title setStringValue:
1761 [NSString stringWithUTF8String: XSTRING (head)->data]];
1762 else if (isQ == YES)
1763 [title setStringValue: @"Question"];
1764 else
1765 [title setStringValue: @"Information"];
1766
1767 {
1768 int i;
1769 NSRect r, s, t;
1770
1771 if (cols == 1 && rows > 1) /* Never told where to split */
1772 {
1773 [matrix addColumn];
1774 for (i = 0; i<rows/2; i++)
1775 {
1776 [matrix putCell: [matrix cellAtRow: (rows+1)/2 column: 0]
1777 atRow: i column: 1];
1778 [matrix removeRow: (rows+1)/2];
1779 }
1780 }
1781
1782 [matrix sizeToFit];
1783 {
1784 NSSize csize = [matrix cellSize];
1785 if (csize.width < MINCELLWIDTH)
1786 {
1787 csize.width = MINCELLWIDTH;
1788 [matrix setCellSize: csize];
1789 [matrix sizeToCells];
1790 }
1791 }
1792
1793 [title sizeToFit];
1794 [command sizeToFit];
1795
1796 t = [matrix frame];
1797 r = [title frame];
1798 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1799 {
1800 t.origin.x = r.origin.x;
1801 t.size.width = r.size.width;
1802 }
1803 r = [command frame];
1804 if (r.size.width+r.origin.x > t.size.width+t.origin.x)
1805 {
1806 t.origin.x = r.origin.x;
1807 t.size.width = r.size.width;
1808 }
1809
1810 r = [self frame];
1811 s = [(NSView *)[self contentView] frame];
1812 r.size.width += t.origin.x+t.size.width +2*SPACER-s.size.width;
1813 r.size.height += t.origin.y+t.size.height+SPACER-s.size.height;
1814 [self setFrame: r display: NO];
1815 }
1816
1817 return self;
1818}
1819
1820
1821- (void)dealloc
1822{
1823 { [super dealloc]; return; };
1824}
1825
1826
1827- (Lisp_Object)runDialogAt: (NSPoint)p
1828{
1829 NSEvent *e;
1830 NSModalSession session;
1831 int ret;
1832
1833 [self center]; /*XXX p ignored? */
1834 [self orderFront: NSApp];
1835
1836 session = [NSApp beginModalSessionForWindow: self];
1837 while ((ret = [NSApp runModalSession: session]) == NSRunContinuesResponse)
1838 {
1839 (e = [NSApp nextEventMatchingMask: NSAnyEventMask
1840 untilDate: [NSDate distantFuture]
1841 inMode: NSEventTrackingRunLoopMode
1842 dequeue: NO]);
1843/*fprintf (stderr, "ret = %d\te = %p\n", ret, e); */
1844 }
1845 [NSApp endModalSession: session];
1846
1847 return (Lisp_Object)ret;
1848}
1849
1850@end
1851
1852
1853
1854/* ==========================================================================
1855
1856 Lisp definitions
1857
1858 ========================================================================== */
1859
1860DEFUN ("ns-reset-menu", Fns_reset_menu, Sns_reset_menu, 0, 0, 0,
1861 "Cause the NS menu to be re-calculated.")
1862 ()
1863{
1864 set_frame_menubar (SELECTED_FRAME (), 1, 0);
1865 return Qnil;
1866}
1867
1868
1869DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
1870 "Pop up a deck-of-cards menu and return user's selection.\n\
1871POSITION is a position specification. This is either a mouse button event\n\
1872or a list ((XOFFSET YOFFSET) WINDOW)\n\
1873where XOFFSET and YOFFSET are positions in pixels from the top left\n\
1874corner of WINDOW's frame. (WINDOW may be a frame object instead of a window.)\n\
1875This controls the position of the center of the first line\n\
1876in the first pane of the menu, not the top left of the menu as a whole.\n\
1877\n\
1878MENU is a specifier for a menu. For the simplest case, MENU is a keymap.\n\
1879The menu items come from key bindings that have a menu string as well as\n\
1880a definition; actually, the \"definition\" in such a key binding looks like\n\
1881\(STRING . REAL-DEFINITION). To give the menu a title, put a string into\n\
1882the keymap as a top-level element.\n\n\
1883You can also use a list of keymaps as MENU.\n\
1884 Then each keymap makes a separate pane.\n\
1885When MENU is a keymap or a list of keymaps, the return value\n\
1886is a list of events.\n\n\
1887Alternatively, you can specify a menu of multiple panes\n\
1888 with a list of the form (TITLE PANE1 PANE2...),\n\
1889where each pane is a list of form (TITLE ITEM1 ITEM2...).\n\
1890Each ITEM is normally a cons cell (STRING . VALUE);\n\
1891but a string can appear as an item--that makes a nonselectable line\n\
1892in the menu.\n\
1893With this form of menu, the return value is VALUE from the chosen item.")
1894 (position, menu)
1895 Lisp_Object position, menu;
1896{
1897 return ns_popup_menu (position, menu);
1898}
1899
1900
1901DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
1902 doc: /* Pop up a dialog box and return user's selection.
1903POSITION specifies which frame to use.
1904This is normally a mouse button event or a window or frame.
1905If POSITION is t, it means to use the frame the mouse is on.
1906The dialog box appears in the middle of the specified frame.
1907
1908CONTENTS specifies the alternatives to display in the dialog box.
1909It is a list of the form (DIALOG ITEM1 ITEM2...).
1910Each ITEM is a cons cell (STRING . VALUE).
1911The return value is VALUE from the chosen item.
1912
1913An ITEM may also be just a string--that makes a nonselectable item.
1914An ITEM may also be nil--that means to put all preceding items
1915on the left of the dialog box and all following items on the right.
1916\(By default, approximately half appear on each side.)
1917
1918If HEADER is non-nil, the frame title for the box is "Information",
1919otherwise it is "Question".
1920
1921If the user gets rid of the dialog box without making a valid choice,
1922for instance using the window manager, then this produces a quit and
1923`x-popup-dialog' does not return. */)
1924 (position, contents, header)
1925 Lisp_Object position, contents, header;
1926{
1927 return ns_popup_dialog (position, contents, header);
1928}
1929
1930
1931/* ==========================================================================
1932
1933 Lisp interface declaration
1934
1935 ========================================================================== */
1936
1937void
1938syms_of_nsmenu ()
1939{
1940 defsubr (&Sx_popup_menu);
1941 defsubr (&Sx_popup_dialog);
1942 defsubr (&Sns_reset_menu);
1943 staticpro (&menu_items);
1944 menu_items = Qnil;
1945
1946 Qdebug_on_next_call = intern ("debug-on-next-call");
1947 staticpro (&Qdebug_on_next_call);
1948}