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