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