diff options
Diffstat (limited to 'src/macmenu.c')
-rw-r--r-- | src/macmenu.c | 595 |
1 files changed, 585 insertions, 10 deletions
diff --git a/src/macmenu.c b/src/macmenu.c index 14bfe92cb25..3ea09412650 100644 --- a/src/macmenu.c +++ b/src/macmenu.c @@ -1,6 +1,6 @@ /* Menu support for GNU Emacs on Mac OS. Copyright (C) 2000, 2001, 2002, 2003, 2004, - 2005, 2006 Free Software Foundation, Inc. + 2005, 2006, 2007 Free Software Foundation, Inc. This file is part of GNU Emacs. @@ -77,10 +77,11 @@ 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 */ @@ -876,6 +877,32 @@ no quit occurs and `x-popup-menu' returns nil. */) #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. @@ -961,6 +988,96 @@ for instance using the window manager, then this produces a quit and 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; + + 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. */ @@ -1450,6 +1567,80 @@ update_submenu_strings (first_wv) } +#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). */ @@ -1463,8 +1654,6 @@ menu_quit_handler (nextHandler, theEvent, userData) OSStatus err; UInt32 keyCode; UInt32 keyModifiers; - extern int mac_quit_char_modifiers; - extern int mac_quit_char_keycode; err = GetEventParameter (theEvent, kEventParamKeyCode, typeUInt32, NULL, sizeof(UInt32), NULL, &keyCode); @@ -1474,8 +1663,7 @@ menu_quit_handler (nextHandler, theEvent, userData) typeUInt32, NULL, sizeof(UInt32), NULL, &keyModifiers); - if (err == noErr && 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 (); @@ -2132,8 +2320,390 @@ mac_menu_show (f, x, y, for_click, keymaps, title, error) #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) { @@ -2238,6 +2808,7 @@ mac_dialog (widget_value *wv) return i; } +#endif /* not TARGET_API_MAC_CARBON */ static char * button_names [] = { "button1", "button2", "button3", "button4", "button5", @@ -2370,10 +2941,10 @@ mac_dialog_show (f, keymaps, title, header, error_name) } /* 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. */ @@ -2485,6 +3056,10 @@ add_menu_item (menu, pos, wv) 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); |