/* NeXT/Open/GNUstep and MacOSX Cocoa menu and toolbar module.
- Copyright (C) 2007, 2008 Free Software Foundation, Inc.
+ Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
This file is part of GNU Emacs.
Christian Limpach, Scott Bender, Christophe de Dinechin) and code in the
Carbon version by Yamamoto Mitsuharu. */
+/* This should be the first include, as it may set up #defines affecting
+ interpretation of even the system includes. */
#include "config.h"
+#include <setjmp.h>
+
#include "lisp.h"
#include "window.h"
#include "buffer.h"
#include "nsterm.h"
#include "termhooks.h"
#include "keyboard.h"
+#include "menu.h"
+
+#define NSMENUPROFILE 0
-/* for profiling */
+#if NSMENUPROFILE
#include <sys/timeb.h>
#include <sys/types.h>
+#endif
#define MenuStagger 10.0
extern Lisp_Object Qundefined, Qmenu_enable, Qmenu_bar_update_hook;
extern Lisp_Object QCtoggle, QCradio;
-extern Lisp_Object Vmenu_updating_frame;
-
Lisp_Object Qdebug_on_next_call;
extern Lisp_Object Voverriding_local_map, Voverriding_local_map_menu_flag,
Qoverriding_local_map, Qoverriding_terminal_local_map;
/* Nonzero means a menu is currently active. */
static int popup_activated_flag;
+static NSModalSession popupSession;
/* NOTE: toolbar implementation is at end,
following complete menu implementation. */
========================================================================== */
-/*23: FIXME: not currently used, but should normalize with other terms. */
+/* FIXME: not currently used, but should normalize with other terms. */
void
x_activate_menubar (struct frame *f)
{
2) deep_p = 1, submenu = nil: Recompute all submenus.
3) deep_p = 1, submenu = non-nil: Update contents of a single submenu.
-------------------------------------------------------------------------- */
-/*#define NSMENUPROFILE 1 */
void
ns_update_menubar (struct frame *f, int deep_p, EmacsMenu *submenu)
{
widget_value *wv, *first_wv, *prev_wv = 0;
int i;
-#ifdef NSMENUPROFILE
+#if NSMENUPROFILE
struct timeb tb;
long t;
#endif
if (menu == nil)
{
- menu = [[EmacsMenu alloc] initWithTitle: @"Emacs"];
+ menu = [[EmacsMenu alloc] initWithTitle: ns_app_name];
needsSet = YES;
}
else
[attMenu close];
}
-#ifdef NSMENUPROFILE
+#if NSMENUPROFILE
ftime (&tb);
t = -(1000*tb.time+tb.millitm);
#endif
- /* widget_value is a straightforward object translation of emacs's
- Byzantine lisp menu structures */
- wv = xmalloc_widget_value ();
- wv->name = "Emacs";
- wv->value = 0;
- wv->enabled = 1;
- wv->button_type = BUTTON_TYPE_NONE;
- wv->help = Qnil;
- first_wv = wv;
-
#ifdef NS_IMPL_GNUSTEP
deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */
#endif
{
/* No change.. */
-#ifdef NSMENUPROFILE
+#if NSMENUPROFILE
ftime (&tb);
t += 1000*tb.time+tb.millitm;
fprintf (stderr, "NO CHANGE! CUTTING OUT after %ld msec.\n", t);
int n;
Lisp_Object string;
+ wv = xmalloc_widget_value ();
+ wv->name = "menubar";
+ wv->value = 0;
+ wv->enabled = 1;
+ wv->button_type = BUTTON_TYPE_NONE;
+ wv->help = Qnil;
+ first_wv = wv;
+
/* Make widget-value tree w/ just the top level menu bar strings */
items = FRAME_MENU_BAR_ITEMS (f);
if (NILP (items))
free_menubar_widget_value_tree (first_wv);
-#ifdef NSMENUPROFILE
+#if NSMENUPROFILE
ftime (&tb);
t += 1000*tb.time+tb.millitm;
fprintf (stderr, "Menu update took %ld msec.\n", t);
to set_frame_menubar */
- (void)menuNeedsUpdate: (NSMenu *)menu
{
- NSEvent *event = [[FRAME_NS_VIEW (frame) window] currentEvent];
+ NSEvent *event;
+ if (!FRAME_LIVE_P (frame))
+ return;
+ event = [[FRAME_NS_VIEW (frame) window] currentEvent];
/* HACK: Cocoa/Carbon will request update on every keystroke
via IsMenuKeyEvent -> CheckMenusForKeyEvent. These are not needed
since key equivalents are handled through emacs.
On Leopard, even keystroke events generate SystemDefined events, but
their subtype is 8. */
- if ([event type] != NSSystemDefined || [event subtype] == 8)
+ if ([event type] != NSSystemDefined || [event subtype] == 8
+ /* Also, don't try this if from an event picked up asynchronously,
+ as lots of lisp evaluation happens in ns_update_menubar. */
+ || handling_signal != 0)
return;
/*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */
ns_update_menubar (frame, 1, self);
}
-/* parse a wdiget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
- into an accelerator string */
+/* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
+ into an accelerator string. We are only able to display a single character
+ for an accelerator, together with an optional modifier combination. (Under
+ Carbon more control was possible, but in Cocoa multi-char strings passed to
+ NSMenuItem get ignored. For now we try to display a super-single letter
+ combo, and return the others as strings to be appended to the item title.
+ (This is signaled by setting keyEquivModMask to 0 for now.) */
-(NSString *)parseKeyEquiv: (char *)key
{
char *tpos = key;
- keyEquivModMask = 0;
- /* currently we just parse 'super' combinations;
- later we'll set keyEquivModMask */
+ keyEquivModMask = NSCommandKeyMask;
+
if (!key || !strlen (key))
return @"";
while (*tpos == ' ' || *tpos == '(')
tpos++;
- if (*tpos != 's'/* || tpos[3] != ')'*/)
- return @"";
- return [NSString stringWithFormat: @"%c", tpos[2]];
+ if ((*tpos == 's') && (*(tpos+1) == '-'))
+ {
+ return [NSString stringWithFormat: @"%c", tpos[2]];
+ }
+ keyEquivModMask = 0; /* signal */
+ return [NSString stringWithUTF8String: tpos];
}
title = @"< ? >"; /* (get out in the open so we know about it) */
keyEq = [self parseKeyEquiv: wv->key];
+#ifdef NS_IMPL_COCOA
+ /* OS X just ignores modifier strings longer than one character */
+ if (keyEquivModMask == 0)
+ title = [title stringByAppendingFormat: @" (%@)", keyEq];
+#endif
item = [self addItemWithTitle: (NSString *)title
action: @selector (menuDown:)
keyEquivalent: keyEq];
- if (keyEquivModMask)
- [item setKeyEquivalentModifierMask: keyEquivModMask];
+ [item setKeyEquivalentModifierMask: keyEquivModMask];
[item setEnabled: wv->enabled];
else
[item setState: NSOffState];
- [item setTag: (int)wv->call_data];
+ [item setTag: (NSInteger)wv->call_data];
}
return item;
/* convenience */
--(void) clear
+-(void)clear
{
int n;
{
NSMenuItem *item = [self itemAtIndex: n];
NSString *title = [item title];
- if (([title length] == 0 || [@"Apple" isEqualToString: title])
+ if (([title length] == 0 /* OSX 10.5 */
+ || [ns_app_name isEqualToString: title] /* from 10.6 on */
+ || [@"Apple" isEqualToString: title]) /* older */
&& ![item isSeparatorItem])
continue;
[self removeItemAtIndex: n];
if (wv->contents)
{
- EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: @"Submenu"];
+ EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: [item title]];
[self setSubmenu: submenu forItem: item];
[submenu fillWithWidgetValue: wv->contents];
========================================================================== */
-static Lisp_Object
-cleanup_popup_menu (Lisp_Object arg)
-{
- discard_menu_items ();
- return Qnil;
-}
-
-
-static Lisp_Object
-ns_popup_menu (Lisp_Object position, Lisp_Object menu)
+Lisp_Object
+ns_menu_show (FRAME_PTR f, int x, int y, int for_click, int keymaps,
+ Lisp_Object title, char **error)
{
EmacsMenu *pmenu;
- struct frame *f = NULL;
NSPoint p;
- Lisp_Object window, x, y, tem, keymap, title;
- struct gcpro gcpro1;
- int specpdl_count = SPECPDL_INDEX (), specpdl_count2;
- char *error_name = NULL;
- int keymaps = 0;
+ Lisp_Object window, tem, keymap;
+ int specpdl_count = SPECPDL_INDEX ();
widget_value *wv, *first_wv = 0;
- NSTRACE (ns_popup_menu);
-
- if (!NILP (position))
- {
- check_ns ();
-
- if (EQ (position, Qt)
- || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
- || EQ (XCAR (position), Qtool_bar))))
- {
- /* Use the mouse's current position. */
- struct frame *new_f = SELECTED_FRAME ();
-
- if (FRAME_TERMINAL (new_f)->mouse_position_hook)
- (*FRAME_TERMINAL (new_f)->mouse_position_hook)
- (&new_f, 0, 0, 0, &x, &y, 0);
- if (new_f != 0)
- XSETFRAME (window, new_f);
- else
- {
- window = selected_window;
- x = make_number (0);
- y = make_number (0);
- }
- }
- else
- {
- CHECK_CONS (position);
- tem = Fcar (position);
- if (XTYPE (tem) == Lisp_Cons)
- {
- window = Fcar (Fcdr (position));
- x = Fcar (tem);
- y = Fcar (Fcdr (tem));
- }
- else
- {
- tem = Fcar (Fcdr (position));
- window = Fcar (tem);
- tem = Fcar (Fcdr (Fcdr (tem)));
- x = Fcar (tem);
- y = Fcdr (tem);
- }
- }
-
- CHECK_NUMBER (x);
- CHECK_NUMBER (y);
-
- if (FRAMEP (window))
- {
- f = XFRAME (window);
-
- p.x = 0;
- p.y = 0;
- }
- else
- {
- struct window *win = XWINDOW (window);
- CHECK_LIVE_WINDOW (window);
- f = XFRAME (WINDOW_FRAME (win));
- p.x = FRAME_COLUMN_WIDTH (f) * WINDOW_LEFT_EDGE_COL (win);
- p.y = FRAME_LINE_HEIGHT (f) * WINDOW_TOP_EDGE_LINE (win);
- }
-
- p.x += XINT (x); p.y += XINT (y);
-
- XSETFRAME (Vmenu_updating_frame, f);
- }
- else
- { /* no position given */
- /* FIXME: if called during dump, we need to stop precomputation of
- key equivalents (see below) because the keydefs in ns-win.el have
- not been loaded yet. */
- if (noninteractive)
- return Qnil;
- Vmenu_updating_frame = Qnil;
- }
-
- /* now parse the lisp menus */
- record_unwind_protect (unuse_menu_items, Qnil);
- title = Qnil;
- GCPRO1 (title);
-
- /* Decode the menu items from what was specified. */
-
- keymap = get_keymap (menu, 0, 0);
- if (CONSP (keymap))
- {
- /* We were given a keymap. Extract menu info from the keymap. */
- Lisp_Object prompt;
-
- /* Extract the detailed info to make one pane. */
- keymap_panes (&menu, 1, NILP (position));
-
- /* Search for a string appearing directly as an element of the keymap.
- That string is the title of the menu. */
- prompt = Fkeymap_prompt (keymap);
- title = NILP (prompt) ? build_string ("Select") : prompt;
-
- /* Make that be the pane title of the first pane. */
- if (!NILP (prompt) && menu_items_n_panes >= 0)
- XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt;
-
- keymaps = 1;
- }
- else if (CONSP (menu) && KEYMAPP (XCAR (menu)))
- {
- /* We were given a list of keymaps. */
- int nmaps = XFASTINT (Flength (menu));
- Lisp_Object *maps
- = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object));
- int i;
-
- title = Qnil;
-
- /* The first keymap that has a prompt string
- supplies the menu title. */
- for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem))
- {
- Lisp_Object prompt;
-
- maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0);
-
- prompt = Fkeymap_prompt (keymap);
- if (NILP (title) && !NILP (prompt))
- title = prompt;
- }
-
- /* Extract the detailed info to make one pane. */
- keymap_panes (maps, nmaps, NILP (position));
-
- /* Make the title be the pane title of the first pane. */
- if (!NILP (title) && menu_items_n_panes >= 0)
- XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title;
-
- keymaps = 1;
- }
- else
- {
- /* We were given an old-fashioned menu. */
- title = Fcar (menu);
- CHECK_STRING (title);
-
- list_of_panes (Fcdr (menu));
-
- keymaps = 0;
- }
-
- unbind_to (specpdl_count, Qnil);
-
- /* If no position given, that was a signal to just precompute and cache
- key equivalents, which was a side-effect of what we just did. */
- if (NILP (position))
- {
- discard_menu_items ();
- UNGCPRO;
- return Qnil;
- }
-
- record_unwind_protect (cleanup_popup_menu, Qnil);
- BLOCK_INPUT;
+ p.x = x; p.y = y;
/* now parse stage 2 as in ns_update_menubar */
wv = xmalloc_widget_value ();
wv->help = Qnil;
first_wv = wv;
- specpdl_count2 = SPECPDL_INDEX ();
-
#if 0
/* FIXME: a couple of one-line differences prevent reuse */
wv = digest_single_submenu (0, menu_items_used, Qnil);
[NSString stringWithUTF8String: SDATA (title)]];
[pmenu fillWithWidgetValue: first_wv->contents];
free_menubar_widget_value_tree (first_wv);
- unbind_to (specpdl_count2, Qnil);
+ unbind_to (specpdl_count, Qnil);
popup_activated_flag = 1;
tem = [pmenu runMenuAt: p forFrame: f keymaps: keymaps];
popup_activated_flag = 0;
[[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
- UNBLOCK_INPUT;
- discard_menu_items ();
- unbind_to (specpdl_count, Qnil);
- UNGCPRO;
-
- if (error_name) error (error_name);
return tem;
}
-
-
/* ==========================================================================
Toolbar: externally-called functions
item = [[[NSToolbarItem alloc] initWithItemIdentifier: identifier]
autorelease];
[item setImage: img];
- [item setToolTip: [NSString stringWithCString: help]];
+ [item setToolTip: [NSString stringWithUTF8String: help]];
[item setTarget: emacsView];
[item setAction: @selector (toolbarClicked:)];
}
{
NSString *str = [NSString stringWithUTF8String: text];
NSRect r = [textField frame];
- r.size.width = [[[textField font] screenFont] widthOfString: str] + 8;
+ NSSize textSize = [str sizeWithAttributes:
+ [NSDictionary dictionaryWithObject: [[textField font] screenFont]
+ forKey: NSFontAttributeName]];
+ NSSize padSize = [[[textField font] screenFont]
+ boundingRectForFont].size;
+
+ r.size.width = textSize.width + padSize.width/2;
+ r.size.height = textSize.height + padSize.height/2;
[textField setFrame: r];
[textField setStringValue: str];
}
========================================================================== */
+
+static Lisp_Object
+pop_down_menu (Lisp_Object arg)
+{
+ struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
+ if (popup_activated_flag)
+ {
+ popup_activated_flag = 0;
+ BLOCK_INPUT;
+ [NSApp endModalSession: popupSession];
+ [((EmacsDialogPanel *) (p->pointer)) close];
+ [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
+ UNBLOCK_INPUT;
+ }
+ return Qnil;
+}
+
+
Lisp_Object
ns_popup_dialog (Lisp_Object position, Lisp_Object contents, Lisp_Object header)
{
isQ = NILP (header);
- if (EQ (position, Qt))
+ if (EQ (position, Qt)
+ || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar)
+ || EQ (XCAR (position), Qtool_bar))))
{
window = selected_window;
}
window = Fcar (tem); /* POSN_WINDOW (tem) */
}
}
- else if (FRAMEP (position))
+ else if (WINDOWP (position) || FRAMEP (position))
{
window = position;
}
else
- {
- CHECK_LIVE_WINDOW (position);
- window = position;
- }
-
+ window = Qnil;
+
if (FRAMEP (window))
f = XFRAME (window);
- else
+ else if (WINDOWP (window))
{
CHECK_LIVE_WINDOW (window);
f = XFRAME (WINDOW_FRAME (XWINDOW (window)));
}
+ else
+ CHECK_WINDOW (window);
+
p.x = (int)f->left_pos + ((int)FRAME_COLUMN_WIDTH (f) * f->text_cols)/2;
p.y = (int)f->top_pos + (FRAME_LINE_HEIGHT (f) * f->text_lines)/2;
+
+ BLOCK_INPUT;
dialog = [[EmacsDialogPanel alloc] initFromContents: contents
isQuestion: isQ];
- popup_activated_flag = 1;
- tem = [dialog runDialogAt: p];
- popup_activated_flag = 0;
-
- [dialog close];
+ {
+ int specpdl_count = SPECPDL_INDEX ();
+ record_unwind_protect (pop_down_menu, make_save_value (dialog, 0));
+ popup_activated_flag = 1;
+ tem = [dialog runDialogAt: p];
+ unbind_to (specpdl_count, Qnil); /* calls pop_down_menu */
+ }
+ UNBLOCK_INPUT;
- [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow];
return tem;
}
#define TEXTHEIGHT 20.0
#define MINCELLWIDTH 90.0
-- initWithContentRect: (NSRect)contentRect styleMask: (unsigned int)aStyle
+- initWithContentRect: (NSRect)contentRect styleMask: (NSUInteger)aStyle
backing: (NSBackingStoreType)backingType defer: (BOOL)flag
{
NSSize spacing = {SPACER, SPACER};
area.size.height= TEXTHEIGHT;
command = [[[NSTextField alloc] initWithFrame: area] autorelease];
[[self contentView] addSubview: command];
- [command setStringValue: @"Emacs"];
+ [command setStringValue: ns_app_name];
[command setDrawsBackground: NO];
[command setBezeled: NO];
[command setSelectable: NO];
item = XCAR (list);
if (XTYPE (item) == Lisp_String)
{
- [window addString: XSTRING (item)->data row: row++];
+ [window addString: SDATA (item) row: row++];
}
else if (XTYPE (item) == Lisp_Cons)
{
- [window addButton: XSTRING (XCAR (item))->data
+ [window addButton: SDATA (XCAR (item))
value: XCDR (item) row: row++];
}
else if (NILP (item))
if (XTYPE (head) == Lisp_String)
[title setStringValue:
- [NSString stringWithUTF8String: XSTRING (head)->data]];
+ [NSString stringWithUTF8String: SDATA (head)]];
else if (isQ == YES)
[title setStringValue: @"Question"];
else
- (Lisp_Object)runDialogAt: (NSPoint)p
{
- NSEvent *e;
- NSModalSession session;
- int ret;
-
- [self center]; /*XXX p ignored? */
- [self orderFront: NSApp];
-
- session = [NSApp beginModalSessionForWindow: self];
- while ((ret = [NSApp runModalSession: session]) == NSRunContinuesResponse)
+ NSInteger ret;
+ extern EMACS_TIME timer_check (int do_it_now); /* TODO: add to a header */
+
+ /* initiate a session that will be ended by pop_down_menu */
+ popupSession = [NSApp beginModalSessionForWindow: self];
+ while (popup_activated_flag
+ && (ret = [NSApp runModalSession: popupSession])
+ == NSRunContinuesResponse)
{
- (e = [NSApp nextEventMatchingMask: NSAnyEventMask
- untilDate: [NSDate distantFuture]
- inMode: NSEventTrackingRunLoopMode
- dequeue: NO]);
-/*fprintf (stderr, "ret = %d\te = %p\n", ret, e); */
+ /* Run this for timers.el, indep of atimers; might not return.
+ TODO: use return value to avoid calling every iteration. */
+ timer_check (1);
+ [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
}
- [NSApp endModalSession: session];
- { // FIXME: BIG UGLY HACK!!!
+ { /* FIXME: BIG UGLY HACK!!! */
Lisp_Object tmp;
*(EMACS_INT*)(&tmp) = ret;
return tmp;
@end
-
/* ==========================================================================
Lisp definitions
}
-DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
- doc: /* Pop up a deck-of-cards menu and return user's selection.
-POSITION is a position specification. This is either a mouse button event
-or a list ((XOFFSET YOFFSET) WINDOW)
-where XOFFSET and YOFFSET are positions in pixels from the top left
-corner of WINDOW. (WINDOW may be a window or a frame object.)
-This controls the position of the top left of the menu as a whole.
-If POSITION is t, it means to use the current mouse position.
-
-MENU is a specifier for a menu. For the simplest case, MENU is a keymap.
-The menu items come from key bindings that have a menu string as well as
-a definition; actually, the \"definition\" in such a key binding looks like
-\(STRING . REAL-DEFINITION). To give the menu a title, put a string into
-the keymap as a top-level element.
-
-If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.
-Otherwise, REAL-DEFINITION should be a valid key binding definition.
-
-You can also use a list of keymaps as MENU.
- Then each keymap makes a separate pane.
-
-When MENU is a keymap or a list of keymaps, the return value is the
-list of events corresponding to the user's choice. Note that
-`x-popup-menu' does not actually execute the command bound to that
-sequence of events.
-
-Alternatively, you can specify a menu of multiple panes
- with a list of the form (TITLE PANE1 PANE2...),
-where each pane is a list of form (TITLE ITEM1 ITEM2...).
-Each ITEM is normally a cons cell (STRING . VALUE);
-but a string can appear as an item--that makes a nonselectable line
-in the menu.
-With this form of menu, the return value is VALUE from the chosen item. */)
- (position, menu)
- Lisp_Object position, menu;
-{
- return ns_popup_menu (position, menu);
-}
-
-
DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0,
doc: /* Pop up a dialog box and return user's selection.
POSITION specifies which frame to use.
void
syms_of_nsmenu ()
{
- defsubr (&Sx_popup_menu);
defsubr (&Sx_popup_dialog);
defsubr (&Sns_reset_menu);
defsubr (&Smenu_or_popup_active_p);
- staticpro (&menu_items);
- menu_items = Qnil;
Qdebug_on_next_call = intern ("debug-on-next-call");
staticpro (&Qdebug_on_next_call);