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