/* Menu support for GNU Emacs on Mac OS.
Copyright (C) 2000, 2001, 2002, 2003, 2004,
- 2005, 2006 Free Software Foundation, Inc.
+ 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
This file is part of GNU Emacs.
GNU Emacs is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2, or (at your option)
+the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Emacs is distributed in the hope that it will be useful,
#include "dispextern.h"
-#define POPUP_SUBMENU_ID 235
-#define MIN_POPUP_SUBMENU_ID 512
-#define MIN_MENU_ID 256
-#define MIN_SUBMENU_ID 1
+enum mac_menu_kind { /* Menu ID range */
+ MAC_MENU_APPLE, /* 0 (Reserved by Apple) */
+ MAC_MENU_MENU_BAR, /* 1 .. 233 */
+ MAC_MENU_M_APPLE, /* 234 (== M_APPLE) */
+ MAC_MENU_POPUP, /* 235 */
+ MAC_MENU_DRIVER, /* 236 .. 255 (Reserved) */
+ MAC_MENU_MENU_BAR_SUB, /* 256 .. 16383 */
+ MAC_MENU_POPUP_SUB, /* 16384 .. 32767 */
+ MAC_MENU_END /* 32768 */
+};
+
+static const int min_menu_id[] = {0, 1, 234, 235, 236, 256, 16384, 32768};
#define DIALOG_WINDOW_RESOURCE 130
+#if TARGET_API_MAC_CARBON
#define HAVE_DIALOGS 1
+#endif
#undef HAVE_MULTILINGUAL_MENU
-#undef HAVE_DIALOGS /* TODO: Implement native dialogs. */
/******************************************************************/
/* Definitions copied from lwlib.h */
#define FALSE 0
#endif /* no TRUE */
-Lisp_Object Vmenu_updating_frame;
-
Lisp_Object Qdebug_on_next_call;
+extern Lisp_Object Vmenu_updating_frame;
+
extern Lisp_Object Qmenu_bar, Qmac_apple_event;
extern Lisp_Object QCtoggle, QCradio;
static void list_of_panes P_ ((Lisp_Object));
static void list_of_items P_ ((Lisp_Object));
-static int fill_menu P_ ((MenuHandle, widget_value *, int));
+static void find_and_call_menu_selection P_ ((FRAME_PTR, int, Lisp_Object,
+ void *));
+static int fill_menu P_ ((MenuHandle, widget_value *, enum mac_menu_kind, int));
static void fill_menubar P_ ((widget_value *, int));
-static void dispose_menus P_ ((int));
+static void dispose_menus P_ ((enum mac_menu_kind, int));
\f
/* This holds a Lisp vector that holds the results of decoding
/* Current depth within submenus. */
static int menu_items_submenu_depth;
+/* Nonzero means a menu is currently active. */
+static int popup_activated_flag;
+
/* This is set nonzero after the user activates the menu bar, and set
to zero again after the menu bars are redisplayed by prepare_menu_bar.
While it is nonzero, all calls to set_frame_menubar go deep.
}
}
+/* This undoes save_menu_items, and it is called by the specpdl unwind
+ mechanism. */
+
+static Lisp_Object
+restore_menu_items (saved)
+ Lisp_Object saved;
+{
+ menu_items = XCAR (saved);
+ menu_items_allocated = (VECTORP (menu_items) ? ASIZE (menu_items) : 0);
+ saved = XCDR (saved);
+ menu_items_used = XINT (XCAR (saved));
+ saved = XCDR (saved);
+ menu_items_n_panes = XINT (XCAR (saved));
+ saved = XCDR (saved);
+ menu_items_submenu_depth = XINT (XCAR (saved));
+ return Qnil;
+}
+
+/* Push the whole state of menu_items processing onto the specpdl.
+ It will be restored when the specpdl is unwound. */
+
+static void
+save_menu_items ()
+{
+ Lisp_Object saved = list4 (menu_items,
+ make_number (menu_items_used),
+ make_number (menu_items_n_panes),
+ make_number (menu_items_submenu_depth));
+ record_unwind_protect (restore_menu_items, saved);
+ menu_items = Qnil;
+}
+\f
/* Make the menu_items vector twice as large. */
static void
old = menu_items;
menu_items_allocated *= 2;
+
menu_items = Fmake_vector (make_number (menu_items_allocated), Qnil);
bcopy (XVECTOR (old)->contents, XVECTOR (menu_items)->contents,
old_size * sizeof (Lisp_Object));
Lisp_Object arg;
{
discard_menu_items ();
+ return Qnil;
}
DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0,
#ifdef HAVE_MENUS
+/* Regard ESC and C-g as Cancel even without the Cancel button. */
+
+#ifdef MAC_OSX
+static Boolean
+mac_dialog_modal_filter (dialog, event, item_hit)
+ DialogRef dialog;
+ EventRecord *event;
+ DialogItemIndex *item_hit;
+{
+ Boolean result;
+
+ result = StdFilterProc (dialog, event, item_hit);
+ if (result == false
+ && (event->what == keyDown || event->what == autoKey)
+ && ((event->message & charCodeMask) == kEscapeCharCode
+ || mac_quit_char_key_p (event->modifiers,
+ (event->message & keyCodeMask) >> 8)))
+ {
+ *item_hit = kStdCancelItemIndex;
+ return true;
+ }
+
+ return result;
+}
+#endif
+
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.
but I don't want to make one now. */
CHECK_WINDOW (window);
+#ifdef MAC_OSX
+ /* Special treatment for Fmessage_box, Fyes_or_no_p, and Fy_or_n_p. */
+ if (EQ (position, Qt)
+ && STRINGP (Fcar (contents))
+ && ((!NILP (Fequal (XCDR (contents),
+ Fcons (Fcons (build_string ("OK"), Qt), Qnil)))
+ && EQ (header, Qt))
+ || (!NILP (Fequal (XCDR (contents),
+ Fcons (Fcons (build_string ("Yes"), Qt),
+ Fcons (Fcons (build_string ("No"), Qnil),
+ Qnil))))
+ && NILP (header))))
+ {
+ OSStatus err = noErr;
+ AlertStdCFStringAlertParamRec param;
+ CFStringRef error_string, explanation_string;
+ DialogRef alert;
+ DialogItemIndex item_hit;
+ Lisp_Object tem;
+
+ /* Force a redisplay before showing the dialog. If a frame is
+ created just before showing the dialog, its contents may not
+ have been fully drawn. */
+ Fredisplay (Qt);
+
+ tem = Fstring_match (concat3 (build_string ("\\("),
+ call0 (intern ("sentence-end")),
+ build_string ("\\)\n")),
+ XCAR (contents), Qnil);
+ BLOCK_INPUT;
+ if (NILP (tem))
+ {
+ error_string = cfstring_create_with_string (XCAR (contents));
+ if (error_string == NULL)
+ err = memFullErr;
+ explanation_string = NULL;
+ }
+ else
+ {
+ tem = Fmatch_end (make_number (1));
+ error_string =
+ cfstring_create_with_string (Fsubstring (XCAR (contents),
+ make_number (0), tem));
+ if (error_string == NULL)
+ err = memFullErr;
+ else
+ {
+ XSETINT (tem, XINT (tem) + 1);
+ explanation_string =
+ cfstring_create_with_string (Fsubstring (XCAR (contents),
+ tem, Qnil));
+ if (explanation_string == NULL)
+ {
+ CFRelease (error_string);
+ err = memFullErr;
+ }
+ }
+ }
+ if (err == noErr)
+ err = GetStandardAlertDefaultParams (¶m,
+ kStdCFStringAlertVersionOne);
+ if (err == noErr)
+ {
+ param.movable = true;
+ param.position = kWindowAlertPositionParentWindow;
+ if (NILP (header))
+ {
+ param.defaultText = CFSTR ("Yes");
+ param.otherText = CFSTR ("No");
+#if 0
+ param.cancelText = CFSTR ("Cancel");
+ param.cancelButton = kAlertStdAlertCancelButton;
+#endif
+ }
+ err = CreateStandardAlert (kAlertNoteAlert, error_string,
+ explanation_string, ¶m, &alert);
+ CFRelease (error_string);
+ if (explanation_string)
+ CFRelease (explanation_string);
+ }
+ if (err == noErr)
+ err = RunStandardAlert (alert, mac_dialog_modal_filter, &item_hit);
+ UNBLOCK_INPUT;
+
+ if (err == noErr)
+ {
+ if (item_hit == kStdCancelItemIndex)
+ Fsignal (Qquit, Qnil);
+ else if (item_hit == kStdOkItemIndex)
+ return Qt;
+ else
+ return Qnil;
+ }
+ }
+#endif
#ifndef HAVE_DIALOGS
/* Display a menu with these alternatives
in the middle of frame F. */
FRAME_PTR f;
{
SInt32 menu_choice;
+ SInt16 menu_id, menu_item;
extern Point saved_menu_event_location;
set_frame_menubar (f, 0, 1);
BLOCK_INPUT;
+ popup_activated_flag = 1;
menu_choice = MenuSelect (saved_menu_event_location);
- do_menu_choice (menu_choice);
+ popup_activated_flag = 0;
+ menu_id = HiWord (menu_choice);
+ menu_item = LoWord (menu_choice);
+
+#if !TARGET_API_MAC_CARBON
+ if (menu_id == min_menu_id[MAC_MENU_M_APPLE])
+ do_apple_menu (menu_item);
+ else
+#endif
+ if (menu_id)
+ {
+ MenuHandle menu = GetMenuHandle (menu_id);
+
+ if (menu)
+ {
+ UInt32 refcon;
+
+ GetMenuItemRefCon (menu, menu_item, &refcon);
+ find_and_call_menu_selection (f, f->menu_bar_items_used,
+ f->menu_bar_vector, (void *) refcon);
+ }
+ }
+
+ HiliteMenu (0);
UNBLOCK_INPUT;
}
-/* This callback is called from the menu bar pulldown menu
- when the user makes a selection.
- Figure out what the user chose
- and put the appropriate events into the keyboard buffer. */
+/* Find the menu selection and store it in the keyboard buffer.
+ F is the frame the menu is on.
+ MENU_BAR_ITEMS_USED is the length of VECTOR.
+ VECTOR is an array of menu events for the whole menu. */
-void
-menubar_selection_callback (FRAME_PTR f, int client_data)
+static void
+find_and_call_menu_selection (f, menu_bar_items_used, vector, client_data)
+ FRAME_PTR f;
+ int menu_bar_items_used;
+ Lisp_Object vector;
+ void *client_data;
{
Lisp_Object prefix, entry;
- Lisp_Object vector;
Lisp_Object *subprefix_stack;
int submenu_depth = 0;
int i;
- if (!f)
- return;
entry = Qnil;
- subprefix_stack = (Lisp_Object *) alloca (f->menu_bar_items_used * sizeof (Lisp_Object));
- vector = f->menu_bar_vector;
+ subprefix_stack = (Lisp_Object *) alloca (menu_bar_items_used * sizeof (Lisp_Object));
prefix = Qnil;
i = 0;
- while (i < f->menu_bar_items_used)
+
+ while (i < menu_bar_items_used)
{
if (EQ (XVECTOR (vector)->contents[i], Qnil))
{
buf.arg = entry;
kbd_buffer_store_event (&buf);
- f->output_data.mac->menubar_active = 0;
return;
}
i += MENU_ITEMS_ITEM_LENGTH;
}
}
- f->output_data.mac->menubar_active = 0;
}
/* Allocate a widget_value, blocking input. */
int i;
int submenu_depth = 0;
widget_value **submenu_stack;
+ int panes_seen = 0;
submenu_stack
= (widget_value **) alloca (menu_items_used * sizeof (widget_value *));
Lisp_Object pane_name, prefix;
char *pane_string;
+ panes_seen++;
+
pane_name = XVECTOR (menu_items)->contents[i + MENU_ITEMS_PANE_NAME];
prefix = XVECTOR (menu_items)->contents[i + MENU_ITEMS_PANE_PREFIX];
Lisp_Object item_name, enable, descrip, def, type, selected;
Lisp_Object help;
+ /* All items should be contained in panes. */
+ if (panes_seen == 0)
+ abort ();
+
item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
}
\f
+#if TARGET_API_MAC_CARBON
+extern Lisp_Object Vshow_help_function;
+
+static Lisp_Object
+restore_show_help_function (old_show_help_function)
+ Lisp_Object old_show_help_function;
+{
+ Vshow_help_function = old_show_help_function;
+
+ return Qnil;
+}
+
+static pascal OSStatus
+menu_target_item_handler (next_handler, event, data)
+ EventHandlerCallRef next_handler;
+ EventRef event;
+ void *data;
+{
+ OSStatus err, result;
+ MenuRef menu;
+ MenuItemIndex menu_item;
+ Lisp_Object help;
+ GrafPtr port;
+ int specpdl_count = SPECPDL_INDEX ();
+
+ result = CallNextEventHandler (next_handler, event);
+
+ err = GetEventParameter (event, kEventParamDirectObject, typeMenuRef,
+ NULL, sizeof (MenuRef), NULL, &menu);
+ if (err == noErr)
+ err = GetEventParameter (event, kEventParamMenuItemIndex,
+ typeMenuItemIndex, NULL,
+ sizeof (MenuItemIndex), NULL, &menu_item);
+ if (err == noErr)
+ err = GetMenuItemProperty (menu, menu_item,
+ MAC_EMACS_CREATOR_CODE, 'help',
+ sizeof (Lisp_Object), NULL, &help);
+ if (err != noErr)
+ help = Qnil;
+
+ /* Temporarily bind Vshow_help_function to Qnil because we don't
+ want tooltips during menu tracking. */
+ record_unwind_protect (restore_show_help_function, Vshow_help_function);
+ Vshow_help_function = Qnil;
+ GetPort (&port);
+ show_help_echo (help, Qnil, Qnil, Qnil, 1);
+ SetPort (port);
+ unbind_to (specpdl_count, Qnil);
+
+ return err == noErr ? noErr : result;
+}
+#endif
+
+OSStatus
+install_menu_target_item_handler (window)
+ WindowPtr window;
+{
+ OSStatus err = noErr;
+#if TARGET_API_MAC_CARBON
+ static const EventTypeSpec specs[] =
+ {{kEventClassMenu, kEventMenuTargetItem}};
+ static EventHandlerUPP menu_target_item_handlerUPP = NULL;
+
+ if (menu_target_item_handlerUPP == NULL)
+ menu_target_item_handlerUPP =
+ NewEventHandlerUPP (menu_target_item_handler);
+
+ err = InstallWindowEventHandler (window, menu_target_item_handlerUPP,
+ GetEventTypeCount (specs), specs,
+ NULL, NULL);
+#endif
+ return err;
+}
+
/* Event handler function that pops down a menu on C-g. We can only pop
down menus if CancelMenuTracking is present (OSX 10.3 or later). */
-#ifdef HAVE_CANCELMENUTRACKING
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1030
static pascal OSStatus
menu_quit_handler (nextHandler, theEvent, userData)
EventHandlerCallRef nextHandler;
EventRef theEvent;
void* userData;
{
+ OSStatus err;
UInt32 keyCode;
UInt32 keyModifiers;
- extern int mac_quit_char_modifiers;
- extern int mac_quit_char_keycode;
- GetEventParameter (theEvent, kEventParamKeyCode,
- typeUInt32, NULL, sizeof(UInt32), NULL, &keyCode);
+ err = GetEventParameter (theEvent, kEventParamKeyCode,
+ typeUInt32, NULL, sizeof(UInt32), NULL, &keyCode);
- GetEventParameter (theEvent, kEventParamKeyModifiers,
- typeUInt32, NULL, sizeof(UInt32),
- NULL, &keyModifiers);
+ if (err == noErr)
+ err = GetEventParameter (theEvent, kEventParamKeyModifiers,
+ typeUInt32, NULL, sizeof(UInt32),
+ NULL, &keyModifiers);
- if (keyCode == mac_quit_char_keycode
- && keyModifiers == mac_quit_char_modifiers)
+ if (err == noErr && mac_quit_char_key_p (keyModifiers, keyCode))
{
MenuRef menu = userData != 0
? (MenuRef)userData : AcquireRootMenu ();
return CallNextEventHandler (nextHandler, theEvent);
}
-#endif /* HAVE_CANCELMENUTRACKING */
+#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 1030 */
-/* Add event handler for MENU_HANDLE so we can detect C-g.
- If MENU_HANDLE is NULL, install handler for all menus in the menu bar.
+/* Add event handler to all menus that belong to KIND so we can detect C-g.
+ MENU_HANDLE is the root menu of the tracking session to dismiss
+ when C-g is detected. NULL means the menu bar.
If CancelMenuTracking isn't available, do nothing. */
static void
-install_menu_quit_handler (MenuHandle menu_handle)
+install_menu_quit_handler (kind, menu_handle)
+ enum mac_menu_kind kind;
+ MenuHandle menu_handle;
{
-#ifdef HAVE_CANCELMENUTRACKING
- EventTypeSpec typesList[] = { { kEventClassKeyboard, kEventRawKeyDown } };
- int i = MIN_MENU_ID;
- MenuHandle menu = menu_handle ? menu_handle : GetMenuHandle (i);
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1030
+ static const EventTypeSpec typesList[] =
+ {{kEventClassKeyboard, kEventRawKeyDown}};
+ int id;
- while (menu != NULL)
+#if MAC_OS_X_VERSION_MIN_REQUIRED == 1020
+ if (CancelMenuTracking == NULL)
+ return;
+#endif
+ for (id = min_menu_id[kind]; id < min_menu_id[kind + 1]; id++)
{
- InstallMenuEventHandler (menu, menu_quit_handler,
- GetEventTypeCount (typesList),
- typesList, menu_handle, NULL);
- if (menu_handle) break;
- menu = GetMenuHandle (++i);
- }
+ MenuHandle menu = GetMenuHandle (id);
- i = menu_handle ? MIN_POPUP_SUBMENU_ID : MIN_SUBMENU_ID;
- menu = GetMenuHandle (i);
- while (menu != NULL)
- {
+ if (menu == NULL)
+ break;
InstallMenuEventHandler (menu, menu_quit_handler,
GetEventTypeCount (typesList),
- typesList, menu_handle, NULL);
- menu = GetMenuHandle (++i);
+ typesList, menu_handle, NULL);
}
-#endif /* HAVE_CANCELMENUTRACKING */
+#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 1030 */
}
/* Set the contents of the menubar widgets of frame F.
int *submenu_start, *submenu_end;
int *submenu_top_level_items, *submenu_n_panes;
- /* We must not change the menubar when actually in use. */
- if (f->output_data.mac->menubar_active)
- return;
-
XSETFRAME (Vmenu_updating_frame, f);
if (! menubar_widget)
/* Fill in menu_items with the current menu bar contents.
This can evaluate Lisp code. */
+ save_menu_items ();
+
menu_items = f->menu_bar_vector;
menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
}
set_buffer_internal_1 (prev);
- unbind_to (specpdl_count, Qnil);
/* If there has been no change in the Lisp-level contents
of the menu bar, skip redisplaying it. Just exit. */
+ /* Compare the new menu items with the ones computed last time. */
for (i = 0; i < previous_menu_items_used; i++)
if (menu_items_used == i
|| (!EQ (previous_items[i], XVECTOR (menu_items)->contents[i])))
break;
if (i == menu_items_used && i == previous_menu_items_used && i != 0)
{
+ /* The menu items have not changed. Don't bother updating
+ the menus in any form, since it would be a no-op. */
free_menubar_widget_value_tree (first_wv);
discard_menu_items ();
-
+ unbind_to (specpdl_count, Qnil);
return;
}
+ /* The menu items are different, so store them in the frame. */
+ f->menu_bar_vector = menu_items;
+ f->menu_bar_items_used = menu_items_used;
+
+ /* This calls restore_menu_items to restore menu_items, etc.,
+ as they were outside. */
+ unbind_to (specpdl_count, Qnil);
+
/* Now GC cannot happen during the lifetime of the widget_value,
so it's safe to store data from a Lisp_String. */
wv = first_wv->contents;
wv = wv->next;
}
- f->menu_bar_vector = menu_items;
- f->menu_bar_items_used = menu_items_used;
- discard_menu_items ();
}
else
{
fill_menubar (first_wv->contents, deep_p);
/* Add event handler so we can detect C-g. */
- install_menu_quit_handler (NULL);
+ install_menu_quit_handler (MAC_MENU_MENU_BAR, NULL);
+ install_menu_quit_handler (MAC_MENU_MENU_BAR_SUB, NULL);
free_menubar_widget_value_tree (first_wv);
UNBLOCK_INPUT;
{
struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
FRAME_PTR f = p->pointer;
- MenuHandle menu = GetMenuHandle (POPUP_SUBMENU_ID);
+ MenuHandle menu = GetMenuHandle (min_menu_id[MAC_MENU_POPUP]);
BLOCK_INPUT;
FRAME_MAC_DISPLAY_INFO (f)->grabbed = 0;
/* delete all menus */
- dispose_menus (MIN_POPUP_SUBMENU_ID);
- DeleteMenu (POPUP_SUBMENU_ID);
+ dispose_menus (MAC_MENU_POPUP_SUB, 0);
+ DeleteMenu (min_menu_id[MAC_MENU_POPUP]);
DisposeMenu (menu);
UNBLOCK_INPUT;
char **error;
{
int i;
- UInt32 refcon;
int menu_item_choice;
- int menu_item_selection;
+ UInt32 menu_item_selection;
MenuHandle menu;
Point pos;
widget_value *wv, *save_wv = 0, *first_wv = 0, *prev_wv = 0;
}
/* Actually create the menu. */
- menu = NewMenu (POPUP_SUBMENU_ID, "\p");
+ menu = NewMenu (min_menu_id[MAC_MENU_POPUP], "\p");
InsertMenu (menu, -1);
- fill_menu (menu, first_wv->contents, MIN_POPUP_SUBMENU_ID);
+ fill_menu (menu, first_wv->contents, MAC_MENU_POPUP_SUB,
+ min_menu_id[MAC_MENU_POPUP_SUB]);
/* Free the widget_value objects we used to specify the
contents. */
LocalToGlobal (&pos);
/* No selection has been chosen yet. */
- menu_item_choice = 0;
menu_item_selection = 0;
record_unwind_protect (pop_down_menu, make_save_value (f, 0));
/* Add event handler so we can detect C-g. */
- install_menu_quit_handler (menu);
+ install_menu_quit_handler (MAC_MENU_POPUP, menu);
+ install_menu_quit_handler (MAC_MENU_POPUP_SUB, menu);
/* Display the menu. */
+ popup_activated_flag = 1;
menu_item_choice = PopUpMenuSelect (menu, pos.v, pos.h, 0);
- menu_item_selection = LoWord (menu_item_choice);
+ popup_activated_flag = 0;
/* Get the refcon to find the correct item */
- if (menu_item_selection)
+ if (menu_item_choice)
{
MenuHandle sel_menu = GetMenuHandle (HiWord (menu_item_choice));
- if (sel_menu) {
- GetMenuItemRefCon (sel_menu, menu_item_selection, &refcon);
- }
+
+ if (sel_menu)
+ GetMenuItemRefCon (sel_menu, LoWord (menu_item_choice),
+ &menu_item_selection);
}
- else if (! for_click)
- /* Make "Cancel" equivalent to C-g unless this menu was popped up by
- a mouse press. */
- Fsignal (Qquit, Qnil);
+
+ unbind_to (specpdl_count, Qnil);
/* Find the selected item, and its pane, to return
the proper value. */
{
entry
= XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_VALUE];
- if ((int) (EMACS_INT) refcon == i)
+ if (menu_item_selection == i)
{
if (keymaps != 0)
{
/* Make "Cancel" equivalent to C-g. */
Fsignal (Qquit, Qnil);
- unbind_to (specpdl_count, Qnil);
-
return Qnil;
}
\f
#ifdef HAVE_DIALOGS
-/* Construct native Mac OS menubar based on widget_value tree. */
+/* Construct native Mac OS dialog based on widget_value tree. */
+
+#if TARGET_API_MAC_CARBON
+static pascal OSStatus
+mac_handle_dialog_event (next_handler, event, data)
+ EventHandlerCallRef next_handler;
+ EventRef event;
+ void *data;
+{
+ OSStatus err;
+ WindowRef window = (WindowRef) data;
+
+ switch (GetEventClass (event))
+ {
+ case kEventClassCommand:
+ {
+ HICommand command;
+
+ err = GetEventParameter (event, kEventParamDirectObject,
+ typeHICommand, NULL, sizeof (HICommand),
+ NULL, &command);
+ if (err == noErr)
+ if ((command.commandID & ~0xffff) == 'Bt\0\0')
+ {
+ SetWRefCon (window, command.commandID);
+ err = QuitAppModalLoopForWindow (window);
+
+ return err == noErr ? noErr : eventNotHandledErr;
+ }
+
+ return CallNextEventHandler (next_handler, event);
+ }
+ break;
+
+ case kEventClassKeyboard:
+ {
+ OSStatus result;
+ char char_code;
+
+ result = CallNextEventHandler (next_handler, event);
+ if (result == noErr)
+ return noErr;
+
+ err = GetEventParameter (event, kEventParamKeyMacCharCodes,
+ typeChar, NULL, sizeof (char),
+ NULL, &char_code);
+ if (err == noErr)
+ switch (char_code)
+ {
+ case kEscapeCharCode:
+ err = QuitAppModalLoopForWindow (window);
+ break;
+
+ default:
+ {
+ UInt32 modifiers, key_code;
+
+ err = GetEventParameter (event, kEventParamKeyModifiers,
+ typeUInt32, NULL, sizeof (UInt32),
+ NULL, &modifiers);
+ if (err == noErr)
+ err = GetEventParameter (event, kEventParamKeyCode,
+ typeUInt32, NULL, sizeof (UInt32),
+ NULL, &key_code);
+ if (err == noErr)
+ {
+ if (mac_quit_char_key_p (modifiers, key_code))
+ err = QuitAppModalLoopForWindow (window);
+ else
+ err = eventNotHandledErr;
+ }
+ }
+ break;
+ }
+
+ return err == noErr ? noErr : result;
+ }
+ break;
+
+ default:
+ abort ();
+ }
+}
+
+static OSStatus
+install_dialog_event_handler (window)
+ WindowRef window;
+{
+ static const EventTypeSpec specs[] =
+ {{kEventClassCommand, kEventCommandProcess},
+ {kEventClassKeyboard, kEventRawKeyDown}};
+ static EventHandlerUPP handle_dialog_eventUPP = NULL;
+
+ if (handle_dialog_eventUPP == NULL)
+ handle_dialog_eventUPP = NewEventHandlerUPP (mac_handle_dialog_event);
+ return InstallWindowEventHandler (window, handle_dialog_eventUPP,
+ GetEventTypeCount (specs), specs,
+ window, NULL);
+}
+
+#define DIALOG_LEFT_MARGIN (112)
+#define DIALOG_TOP_MARGIN (24)
+#define DIALOG_RIGHT_MARGIN (24)
+#define DIALOG_BOTTOM_MARGIN (20)
+#define DIALOG_MIN_INNER_WIDTH (338)
+#define DIALOG_MAX_INNER_WIDTH (564)
+#define DIALOG_BUTTON_BUTTON_HORIZONTAL_SPACE (12)
+#define DIALOG_BUTTON_BUTTON_VERTICAL_SPACE (12)
+#define DIALOG_BUTTON_MIN_WIDTH (68)
+#define DIALOG_TEXT_MIN_HEIGHT (50)
+#define DIALOG_TEXT_BUTTONS_VERTICAL_SPACE (10)
+#define DIALOG_ICON_WIDTH (64)
+#define DIALOG_ICON_HEIGHT (64)
+#define DIALOG_ICON_LEFT_MARGIN (24)
+#define DIALOG_ICON_TOP_MARGIN (15)
+
+static int
+create_and_show_dialog (f, first_wv)
+ FRAME_PTR f;
+ widget_value *first_wv;
+{
+ OSStatus err;
+ char *dialog_name, *message;
+ int nb_buttons, first_group_count, i, result = 0;
+ widget_value *wv;
+ short buttons_height, text_height, inner_width, inner_height;
+ Rect empty_rect, *rects;
+ WindowRef window = NULL;
+ ControlRef *buttons, default_button = NULL, text;
+
+ dialog_name = first_wv->name;
+ nb_buttons = dialog_name[1] - '0';
+ first_group_count = nb_buttons - (dialog_name[4] - '0');
+
+ wv = first_wv->contents;
+ message = wv->value;
+
+ wv = wv->next;
+ SetRect (&empty_rect, 0, 0, 0, 0);
+
+ /* Create dialog window. */
+ err = CreateNewWindow (kMovableModalWindowClass,
+ kWindowStandardHandlerAttribute,
+ &empty_rect, &window);
+ if (err == noErr)
+ err = SetThemeWindowBackground (window, kThemeBrushMovableModalBackground,
+ true);
+ if (err == noErr)
+ err = SetWindowTitleWithCFString (window, (dialog_name[0] == 'Q'
+ ? CFSTR ("Question")
+ : CFSTR ("Information")));
+
+ /* Create button controls and measure their optimal bounds. */
+ if (err == noErr)
+ {
+ buttons = alloca (sizeof (ControlRef) * nb_buttons);
+ rects = alloca (sizeof (Rect) * nb_buttons);
+ for (i = 0; i < nb_buttons; i++)
+ {
+ CFStringRef label = cfstring_create_with_utf8_cstring (wv->value);
+
+ if (label == NULL)
+ err = memFullErr;
+ else
+ {
+ err = CreatePushButtonControl (window, &empty_rect,
+ label, &buttons[i]);
+ CFRelease (label);
+ }
+ if (err == noErr)
+ {
+ if (!wv->enabled)
+ {
+#ifdef MAC_OSX
+ err = DisableControl (buttons[i]);
+#else
+ err = DeactivateControl (buttons[i]);
+#endif
+ }
+ else if (default_button == NULL)
+ default_button = buttons[i];
+ }
+ if (err == noErr)
+ {
+ SInt16 unused;
+
+ rects[i] = empty_rect;
+ err = GetBestControlRect (buttons[i], &rects[i], &unused);
+ }
+ if (err == noErr)
+ {
+ OffsetRect (&rects[i], -rects[i].left, -rects[i].top);
+ if (rects[i].right < DIALOG_BUTTON_MIN_WIDTH)
+ rects[i].right = DIALOG_BUTTON_MIN_WIDTH;
+ else if (rects[i].right > DIALOG_MAX_INNER_WIDTH)
+ rects[i].right = DIALOG_MAX_INNER_WIDTH;
+
+ err = SetControlCommandID (buttons[i],
+ 'Bt\0\0' + (int) wv->call_data);
+ }
+ if (err != noErr)
+ break;
+ wv = wv->next;
+ }
+ }
+
+ /* Layout buttons. rects[i] is set relative to the bottom-right
+ corner of the inner box. */
+ if (err == noErr)
+ {
+ short bottom, right, max_height, left_align_shift;
+
+ inner_width = DIALOG_MIN_INNER_WIDTH;
+ bottom = right = max_height = 0;
+ for (i = 0; i < nb_buttons; i++)
+ {
+ if (right - rects[i].right < - inner_width)
+ {
+ if (i != first_group_count
+ && right - rects[i].right >= - DIALOG_MAX_INNER_WIDTH)
+ inner_width = - (right - rects[i].right);
+ else
+ {
+ bottom -= max_height + DIALOG_BUTTON_BUTTON_VERTICAL_SPACE;
+ right = max_height = 0;
+ }
+ }
+ if (max_height < rects[i].bottom)
+ max_height = rects[i].bottom;
+ OffsetRect (&rects[i], right - rects[i].right,
+ bottom - rects[i].bottom);
+ right = rects[i].left - DIALOG_BUTTON_BUTTON_HORIZONTAL_SPACE;
+ if (i == first_group_count - 1)
+ right -= DIALOG_BUTTON_BUTTON_HORIZONTAL_SPACE;
+ }
+ buttons_height = - (bottom - max_height);
+
+ left_align_shift = - (inner_width + rects[nb_buttons - 1].left);
+ for (i = nb_buttons - 1; i >= first_group_count; i--)
+ {
+ if (bottom != rects[i].bottom)
+ {
+ left_align_shift = - (inner_width + rects[i].left);
+ bottom = rects[i].bottom;
+ }
+ OffsetRect (&rects[i], left_align_shift, 0);
+ }
+ }
+
+ /* Create a static text control and measure its bounds. */
+ if (err == noErr)
+ {
+ CFStringRef message_string;
+ Rect bounds;
+
+ message_string = cfstring_create_with_utf8_cstring (message);
+ if (message_string == NULL)
+ err = memFullErr;
+ else
+ {
+ ControlFontStyleRec text_style;
+
+ text_style.flags = 0;
+ SetRect (&bounds, 0, 0, inner_width, 0);
+ err = CreateStaticTextControl (window, &bounds, message_string,
+ &text_style, &text);
+ CFRelease (message_string);
+ }
+ if (err == noErr)
+ {
+ SInt16 unused;
+
+ bounds = empty_rect;
+ err = GetBestControlRect (text, &bounds, &unused);
+ }
+ if (err == noErr)
+ {
+ text_height = bounds.bottom - bounds.top;
+ if (text_height < DIALOG_TEXT_MIN_HEIGHT)
+ text_height = DIALOG_TEXT_MIN_HEIGHT;
+ }
+ }
+
+ /* Place buttons. */
+ if (err == noErr)
+ {
+ inner_height = (text_height + DIALOG_TEXT_BUTTONS_VERTICAL_SPACE
+ + buttons_height);
+
+ for (i = 0; i < nb_buttons; i++)
+ {
+ OffsetRect (&rects[i], DIALOG_LEFT_MARGIN + inner_width,
+ DIALOG_TOP_MARGIN + inner_height);
+ SetControlBounds (buttons[i], &rects[i]);
+ }
+ }
+
+ /* Place text. */
+ if (err == noErr)
+ {
+ Rect bounds;
+
+ SetRect (&bounds, DIALOG_LEFT_MARGIN, DIALOG_TOP_MARGIN,
+ DIALOG_LEFT_MARGIN + inner_width,
+ DIALOG_TOP_MARGIN + text_height);
+ SetControlBounds (text, &bounds);
+ }
+
+ /* Create the application icon at the upper-left corner. */
+ if (err == noErr)
+ {
+ ControlButtonContentInfo content;
+ ControlRef icon;
+ static const ProcessSerialNumber psn = {0, kCurrentProcess};
+#ifdef MAC_OSX
+ FSRef app_location;
+#else
+ ProcessInfoRec pinfo;
+ FSSpec app_spec;
+#endif
+ SInt16 unused;
+
+ content.contentType = kControlContentIconRef;
+#ifdef MAC_OSX
+ err = GetProcessBundleLocation (&psn, &app_location);
+ if (err == noErr)
+ err = GetIconRefFromFileInfo (&app_location, 0, NULL, 0, NULL,
+ kIconServicesNormalUsageFlag,
+ &content.u.iconRef, &unused);
+#else
+ bzero (&pinfo, sizeof (ProcessInfoRec));
+ pinfo.processInfoLength = sizeof (ProcessInfoRec);
+ pinfo.processAppSpec = &app_spec;
+ err = GetProcessInformation (&psn, &pinfo);
+ if (err == noErr)
+ err = GetIconRefFromFile (&app_spec, &content.u.iconRef, &unused);
+#endif
+ if (err == noErr)
+ {
+ Rect bounds;
+
+ SetRect (&bounds, DIALOG_ICON_LEFT_MARGIN, DIALOG_ICON_TOP_MARGIN,
+ DIALOG_ICON_LEFT_MARGIN + DIALOG_ICON_WIDTH,
+ DIALOG_ICON_TOP_MARGIN + DIALOG_ICON_HEIGHT);
+ err = CreateIconControl (window, &bounds, &content, true, &icon);
+ ReleaseIconRef (content.u.iconRef);
+ }
+ }
+
+ /* Show the dialog window and run event loop. */
+ if (err == noErr)
+ if (default_button)
+ err = SetWindowDefaultButton (window, default_button);
+ if (err == noErr)
+ err = install_dialog_event_handler (window);
+ if (err == noErr)
+ {
+ SizeWindow (window,
+ DIALOG_LEFT_MARGIN + inner_width + DIALOG_RIGHT_MARGIN,
+ DIALOG_TOP_MARGIN + inner_height + DIALOG_BOTTOM_MARGIN,
+ true);
+ err = RepositionWindow (window, FRAME_MAC_WINDOW (f),
+ kWindowAlertPositionOnParentWindow);
+ }
+ if (err == noErr)
+ {
+ SetWRefCon (window, 0);
+ ShowWindow (window);
+ BringToFront (window);
+ err = RunAppModalLoopForWindow (window);
+ }
+ if (err == noErr)
+ {
+ UInt32 command_id = GetWRefCon (window);
+
+ if ((command_id & ~0xffff) == 'Bt\0\0')
+ result = command_id - 'Bt\0\0';
+ }
+
+ if (window)
+ DisposeWindow (window);
+
+ return result;
+}
+#else /* not TARGET_API_MAC_CARBON */
static int
mac_dialog (widget_value *wv)
{
return i;
}
+#endif /* not TARGET_API_MAC_CARBON */
static char * button_names [] = {
"button1", "button2", "button3", "button4", "button5",
first_wv = wv;
}
+ /* Force a redisplay before showing the dialog. If a frame is created
+ just before showing the dialog, its contents may not have been fully
+ drawn. */
+ Fredisplay (Qt);
+
/* Actually create the dialog. */
-#ifdef HAVE_DIALOGS
- menu_item_selection = mac_dialog (first_wv);
+#if TARGET_API_MAC_CARBON
+ menu_item_selection = create_and_show_dialog (f, first_wv);
#else
- menu_item_selection = 0;
+ menu_item_selection = mac_dialog (first_wv);
#endif
/* Free the widget_value objects we used to specify the contents. */
/* Is this item a separator? */
static int
name_is_separator (name)
- char *name;
+ const char *name;
{
- char *start = name;
+ const char *start = name;
/* Check if name string consists of only dashes ('-'). */
while (*name == '-') name++;
EnableMenuItem (menu, pos);
else
DisableMenuItem (menu, pos);
+
+ if (STRINGP (wv->help))
+ SetMenuItemProperty (menu, pos, MAC_EMACS_CREATOR_CODE, 'help',
+ sizeof (Lisp_Object), &wv->help);
#else /* ! TARGET_API_MAC_CARBON */
item_name[sizeof (item_name) - 1] = '\0';
strncpy (item_name, wv->name, sizeof (item_name) - 1);
/* Construct native Mac OS menu based on widget_value tree. */
static int
-fill_menu (menu, wv, submenu_id)
+fill_menu (menu, wv, kind, submenu_id)
MenuHandle menu;
widget_value *wv;
+ enum mac_menu_kind kind;
int submenu_id;
{
int pos;
for (pos = 1; wv != NULL; wv = wv->next, pos++)
{
add_menu_item (menu, pos, wv);
- if (wv->contents)
+ if (wv->contents && submenu_id < min_menu_id[kind + 1])
{
MenuHandle submenu = NewMenu (submenu_id, "\pX");
InsertMenu (submenu, -1);
SetMenuItemHierarchicalID (menu, pos, submenu_id);
- submenu_id = fill_menu (submenu, wv->contents, submenu_id + 1);
+ submenu_id = fill_menu (submenu, wv->contents, kind, submenu_id + 1);
}
}
/* Clean up the menu bar when filled by the entire menu trees. */
if (deep_p)
{
- dispose_menus (MIN_MENU_ID);
- dispose_menus (MIN_SUBMENU_ID);
+ dispose_menus (MAC_MENU_MENU_BAR, 0);
+ dispose_menus (MAC_MENU_MENU_BAR_SUB, 0);
#if !TARGET_API_MAC_CARBON
title_changed_p = 1;
#endif
/* Fill menu bar titles and submenus. Reuse the existing menu bar
titles as much as possible to minimize redraw (if !deep_p). */
- submenu_id = MIN_SUBMENU_ID;
- for (id = MIN_MENU_ID; wv != NULL; wv = wv->next, id++)
+ submenu_id = min_menu_id[MAC_MENU_MENU_BAR_SUB];
+ for (id = min_menu_id[MAC_MENU_MENU_BAR];
+ wv != NULL && id < min_menu_id[MAC_MENU_MENU_BAR + 1];
+ wv = wv->next, id++)
{
strncpy (title, wv->name, 255);
title[255] = '\0';
GetMenuTitle (menu, old_title);
if (!EqualString (title, old_title, false, false))
- SetMenuTitle (menu, title);
+ {
+#ifdef MAC_OSX
+ if (id + 1 == min_menu_id[MAC_MENU_MENU_BAR + 1]
+ || GetMenuRef (id + 1) == NULL)
+ {
+ /* This is a workaround for Mac OS X 10.5 where just
+ calling SetMenuTitle fails to change the title of
+ the last (Help) menu in the menu bar. */
+ DeleteMenu (id);
+ DisposeMenu (menu);
+ menu = NULL;
+ }
+ else
+#endif /* MAC_OSX */
+ SetMenuTitle (menu, title);
+ }
#else /* !TARGET_API_MAC_CARBON */
if (!EqualString (title, (*menu)->menuData, false, false))
{
}
#endif /* !TARGET_API_MAC_CARBON */
}
- else
+
+ if (!menu)
{
menu = NewMenu (id, title);
InsertMenu (menu, 0);
}
if (wv->contents)
- submenu_id = fill_menu (menu, wv->contents, submenu_id);
+ submenu_id = fill_menu (menu, wv->contents, MAC_MENU_MENU_BAR_SUB,
+ submenu_id);
}
- if (GetMenuHandle (id))
+ if (id < min_menu_id[MAC_MENU_MENU_BAR + 1] && GetMenuHandle (id))
{
- dispose_menus (id);
+ dispose_menus (MAC_MENU_MENU_BAR, id);
#if !TARGET_API_MAC_CARBON
title_changed_p = 1;
#endif
#endif
}
+/* Dispose of menus that belong to KIND, and remove them from the menu
+ list. ID is the lower bound of menu IDs that will be processed. */
+
static void
-dispose_menus (id)
+dispose_menus (kind, id)
+ enum mac_menu_kind kind;
int id;
{
- MenuHandle menu;
-
- while ((menu = GetMenuHandle (id)) != NULL)
+ for (id = max (id, min_menu_id[kind]); id < min_menu_id[kind + 1]; id++)
{
+ MenuHandle menu = GetMenuHandle (id);
+
+ if (menu == NULL)
+ break;
DeleteMenu (id);
DisposeMenu (menu);
- id++;
}
}
#endif /* HAVE_MENUS */
+
+/* Detect if a menu is currently active. */
+
+int
+popup_activated ()
+{
+ return popup_activated_flag;
+}
+
+/* The following is used by delayed window autoselection. */
+
+DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
+ doc: /* Return t if a menu or popup dialog is active. */)
+ ()
+{
+ /* Always return Qnil since menu selection functions do not return
+ until a selection has been made or cancelled. */
+ return Qnil;
+}
\f
void
syms_of_macmenu ()
Qdebug_on_next_call = intern ("debug-on-next-call");
staticpro (&Qdebug_on_next_call);
- DEFVAR_LISP ("menu-updating-frame", &Vmenu_updating_frame,
- doc: /* Frame for which we are updating a menu.
-The enable predicate for a menu command should check this variable. */);
- Vmenu_updating_frame = Qnil;
-
defsubr (&Sx_popup_menu);
+ defsubr (&Smenu_or_popup_active_p);
#ifdef HAVE_MENUS
defsubr (&Sx_popup_dialog);
#endif