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