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