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