| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/cocoa/tab_contents/render_view_context_menu_mac.h" |
| |
| #include "base/compiler_specific.h" |
| #import "base/mac/scoped_sending_event.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/render_widget_host_view.h" |
| #include "grit/generated_resources.h" |
| #import "ui/base/cocoa/menu_controller.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| using content::WebContents; |
| |
| namespace { |
| |
| // Retrieves an NSMenuItem which has the specified command_id. This function |
| // traverses the given |model| in the depth-first order. When this function |
| // finds an item whose command_id is the same as the given |command_id|, it |
| // returns the NSMenuItem associated with the item. This function emulates |
| // views::MenuItemViews::GetMenuItemByID() for Mac. |
| NSMenuItem* GetMenuItemByID(ui::MenuModel* model, |
| NSMenu* menu, |
| int command_id) { |
| for (int i = 0; i < model->GetItemCount(); ++i) { |
| NSMenuItem* item = [menu itemAtIndex:i]; |
| if (model->GetCommandIdAt(i) == command_id) |
| return item; |
| |
| ui::MenuModel* submenu = model->GetSubmenuModelAt(i); |
| if (submenu && [item hasSubmenu]) { |
| NSMenuItem* subitem = GetMenuItemByID(submenu, |
| [item submenu], |
| command_id); |
| if (subitem) |
| return subitem; |
| } |
| } |
| return nil; |
| } |
| |
| } // namespace |
| |
| // Obj-C bridge class that is the target of all items in the context menu. |
| // Relies on the tag being set to the command id. |
| |
| RenderViewContextMenuMac::RenderViewContextMenuMac( |
| WebContents* web_contents, |
| const content::ContextMenuParams& params, |
| NSView* parent_view) |
| : RenderViewContextMenu(web_contents, params), |
| speech_submenu_model_(this), |
| bidi_submenu_model_(this), |
| parent_view_(parent_view) { |
| } |
| |
| RenderViewContextMenuMac::~RenderViewContextMenuMac() { |
| } |
| |
| void RenderViewContextMenuMac::PlatformInit() { |
| InitPlatformMenu(); |
| menu_controller_.reset( |
| [[MenuController alloc] initWithModel:&menu_model_ |
| useWithPopUpButtonCell:NO]); |
| |
| // Synthesize an event for the click, as there is no certainty that |
| // [NSApp currentEvent] will return a valid event. |
| NSEvent* currentEvent = [NSApp currentEvent]; |
| NSWindow* window = [parent_view_ window]; |
| NSPoint position = [window mouseLocationOutsideOfEventStream]; |
| NSTimeInterval eventTime = [currentEvent timestamp]; |
| NSEvent* clickEvent = [NSEvent mouseEventWithType:NSRightMouseDown |
| location:position |
| modifierFlags:NSRightMouseDownMask |
| timestamp:eventTime |
| windowNumber:[window windowNumber] |
| context:nil |
| eventNumber:0 |
| clickCount:1 |
| pressure:1.0]; |
| |
| { |
| // Make sure events can be pumped while the menu is up. |
| base::MessageLoop::ScopedNestableTaskAllower allow( |
| base::MessageLoop::current()); |
| |
| // One of the events that could be pumped is |window.close()|. |
| // User-initiated event-tracking loops protect against this by |
| // setting flags in -[CrApplication sendEvent:], but since |
| // web-content menus are initiated by IPC message the setup has to |
| // be done manually. |
| base::mac::ScopedSendingEvent sendingEventScoper; |
| |
| // Show the menu. |
| [NSMenu popUpContextMenu:[menu_controller_ menu] |
| withEvent:clickEvent |
| forView:parent_view_]; |
| } |
| } |
| |
| void RenderViewContextMenuMac::PlatformCancel() { |
| [menu_controller_ cancel]; |
| } |
| |
| void RenderViewContextMenuMac::ExecuteCommand(int command_id, int event_flags) { |
| switch (command_id) { |
| case IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY: |
| LookUpInDictionary(); |
| break; |
| |
| case IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING: |
| StartSpeaking(); |
| break; |
| |
| case IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING: |
| StopSpeaking(); |
| break; |
| |
| case IDC_WRITING_DIRECTION_DEFAULT: |
| // WebKit's current behavior is for this menu item to always be disabled. |
| NOTREACHED(); |
| break; |
| |
| case IDC_WRITING_DIRECTION_RTL: |
| case IDC_WRITING_DIRECTION_LTR: { |
| content::RenderViewHost* view_host = GetRenderViewHost(); |
| WebKit::WebTextDirection dir = WebKit::WebTextDirectionLeftToRight; |
| if (command_id == IDC_WRITING_DIRECTION_RTL) |
| dir = WebKit::WebTextDirectionRightToLeft; |
| view_host->UpdateTextDirection(dir); |
| view_host->NotifyTextDirection(); |
| break; |
| } |
| |
| default: |
| RenderViewContextMenu::ExecuteCommand(command_id, event_flags); |
| break; |
| } |
| } |
| |
| bool RenderViewContextMenuMac::IsCommandIdChecked(int command_id) const { |
| switch (command_id) { |
| case IDC_WRITING_DIRECTION_DEFAULT: |
| return params_.writing_direction_default & |
| WebKit::WebContextMenuData::CheckableMenuItemChecked; |
| case IDC_WRITING_DIRECTION_RTL: |
| return params_.writing_direction_right_to_left & |
| WebKit::WebContextMenuData::CheckableMenuItemChecked; |
| case IDC_WRITING_DIRECTION_LTR: |
| return params_.writing_direction_left_to_right & |
| WebKit::WebContextMenuData::CheckableMenuItemChecked; |
| |
| default: |
| return RenderViewContextMenu::IsCommandIdChecked(command_id); |
| } |
| } |
| |
| bool RenderViewContextMenuMac::IsCommandIdEnabled(int command_id) const { |
| switch (command_id) { |
| case IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY: |
| // This is OK because the menu is not shown when it isn't |
| // appropriate. |
| return true; |
| |
| case IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING: |
| // This is OK because the menu is not shown when it isn't |
| // appropriate. |
| return true; |
| |
| case IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING: { |
| content::RenderWidgetHostView* view = GetRenderViewHost()->GetView(); |
| return view && view->IsSpeaking(); |
| } |
| |
| case IDC_WRITING_DIRECTION_DEFAULT: // Provided to match OS defaults. |
| return params_.writing_direction_default & |
| WebKit::WebContextMenuData::CheckableMenuItemEnabled; |
| case IDC_WRITING_DIRECTION_RTL: |
| return params_.writing_direction_right_to_left & |
| WebKit::WebContextMenuData::CheckableMenuItemEnabled; |
| case IDC_WRITING_DIRECTION_LTR: |
| return params_.writing_direction_left_to_right & |
| WebKit::WebContextMenuData::CheckableMenuItemEnabled; |
| |
| default: |
| return RenderViewContextMenu::IsCommandIdEnabled(command_id); |
| } |
| } |
| |
| bool RenderViewContextMenuMac::GetAcceleratorForCommandId( |
| int command_id, |
| ui::Accelerator* accelerator) { |
| return false; |
| } |
| |
| void RenderViewContextMenuMac::AppendPlatformEditableItems() { |
| // OS X provides a contextual menu to set writing direction for BiDi |
| // languages. |
| // This functionality is exposed as a keyboard shortcut on Windows & Linux. |
| AppendBidiSubMenu(); |
| } |
| |
| void RenderViewContextMenuMac::InitPlatformMenu() { |
| bool has_selection = !params_.selection_text.empty(); |
| |
| if (has_selection) { |
| menu_model_.AddSeparator(ui::NORMAL_SEPARATOR); |
| menu_model_.AddItemWithStringId( |
| IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY, |
| IDS_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY); |
| |
| content::RenderWidgetHostView* view = GetRenderViewHost()->GetView(); |
| if (view && view->SupportsSpeech()) { |
| speech_submenu_model_.AddItemWithStringId( |
| IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING, |
| IDS_SPEECH_START_SPEAKING_MAC); |
| speech_submenu_model_.AddItemWithStringId( |
| IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING, |
| IDS_SPEECH_STOP_SPEAKING_MAC); |
| menu_model_.AddSubMenu( |
| IDC_CONTENT_CONTEXT_SPEECH_MENU, |
| l10n_util::GetStringUTF16(IDS_SPEECH_MAC), |
| &speech_submenu_model_); |
| } |
| } |
| } |
| |
| void RenderViewContextMenuMac::AppendBidiSubMenu() { |
| bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_DEFAULT, |
| l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_DEFAULT)); |
| bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_LTR, |
| l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_LTR)); |
| bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_RTL, |
| l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_RTL)); |
| |
| menu_model_.AddSubMenu( |
| IDC_WRITING_DIRECTION_MENU, |
| l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_MENU), |
| &bidi_submenu_model_); |
| } |
| |
| void RenderViewContextMenuMac::LookUpInDictionary() { |
| content::RenderWidgetHostView* view = GetRenderViewHost()->GetView(); |
| if (view) |
| view->ShowDefinitionForSelection(); |
| } |
| |
| void RenderViewContextMenuMac::StartSpeaking() { |
| content::RenderWidgetHostView* view = GetRenderViewHost()->GetView(); |
| if (view) |
| view->SpeakSelection(); |
| } |
| |
| void RenderViewContextMenuMac::StopSpeaking() { |
| content::RenderWidgetHostView* view = GetRenderViewHost()->GetView(); |
| if (view) |
| view->StopSpeaking(); |
| } |
| |
| void RenderViewContextMenuMac::UpdateMenuItem(int command_id, |
| bool enabled, |
| bool hidden, |
| const string16& title) { |
| NSMenuItem* item = GetMenuItemByID(&menu_model_, [menu_controller_ menu], |
| command_id); |
| if (!item) |
| return; |
| |
| // Update the returned NSMenuItem directly so we can update it immediately. |
| [item setEnabled:enabled]; |
| [item setTitle:SysUTF16ToNSString(title)]; |
| [item setHidden:hidden]; |
| [[item menu] itemChanged:item]; |
| } |