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