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