1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac.h"
7 #include "base/compiler_specific.h"
8 #import "base/mac/scoped_sending_event.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "chrome/app/chrome_command_ids.h"
12 #include "chrome/grit/generated_resources.h"
13 #include "content/public/browser/render_view_host.h"
14 #include "content/public/browser/render_widget_host_view.h"
15 #import "ui/base/cocoa/menu_controller.h"
16 #include "ui/base/l10n/l10n_util.h"
18 using content::WebContents;
22 // Retrieves an NSMenuItem which has the specified command_id. This function
23 // traverses the given |model| in the depth-first order. When this function
24 // finds an item whose command_id is the same as the given |command_id|, it
25 // returns the NSMenuItem associated with the item. This function emulates
26 // views::MenuItemViews::GetMenuItemByID() for Mac.
27 NSMenuItem* GetMenuItemByID(ui::MenuModel* model,
30 for (int i = 0; i < model->GetItemCount(); ++i) {
31 NSMenuItem* item = [menu itemAtIndex:i];
32 if (model->GetCommandIdAt(i) == command_id)
35 ui::MenuModel* submenu = model->GetSubmenuModelAt(i);
36 if (submenu && [item hasSubmenu]) {
37 NSMenuItem* subitem = GetMenuItemByID(submenu,
49 // OSX implemenation of the ToolkitDelegate.
50 // This simply (re)delegates calls to RVContextMenuMac because they do not
51 // have to be componentized.
52 class ToolkitDelegateMac : public RenderViewContextMenuBase::ToolkitDelegate {
54 explicit ToolkitDelegateMac(RenderViewContextMenuMac* context_menu)
55 : context_menu_(context_menu) {}
56 virtual ~ToolkitDelegateMac() {}
60 virtual void Init(ui::SimpleMenuModel* menu_model) OVERRIDE {
61 context_menu_->InitToolkitMenu();
63 virtual void Cancel() OVERRIDE{
64 context_menu_->CancelToolkitMenu();
66 virtual void UpdateMenuItem(int command_id,
69 const base::string16& title) OVERRIDE {
70 context_menu_->UpdateToolkitMenuItem(
71 command_id, enabled, hidden, title);
74 RenderViewContextMenuMac* context_menu_;
75 DISALLOW_COPY_AND_ASSIGN(ToolkitDelegateMac);
78 // Obj-C bridge class that is the target of all items in the context menu.
79 // Relies on the tag being set to the command id.
81 RenderViewContextMenuMac::RenderViewContextMenuMac(
82 content::RenderFrameHost* render_frame_host,
83 const content::ContextMenuParams& params,
85 : RenderViewContextMenu(render_frame_host, params),
86 speech_submenu_model_(this),
87 bidi_submenu_model_(this),
88 parent_view_(parent_view) {
89 scoped_ptr<ToolkitDelegate> delegate(new ToolkitDelegateMac(this));
90 set_toolkit_delegate(delegate.Pass());
93 RenderViewContextMenuMac::~RenderViewContextMenuMac() {
96 void RenderViewContextMenuMac::Show() {
97 menu_controller_.reset(
98 [[MenuController alloc] initWithModel:&menu_model_
99 useWithPopUpButtonCell:NO]);
101 // Synthesize an event for the click, as there is no certainty that
102 // [NSApp currentEvent] will return a valid event.
103 NSEvent* currentEvent = [NSApp currentEvent];
104 NSWindow* window = [parent_view_ window];
106 NSMakePoint(params_.x, NSHeight([parent_view_ bounds]) - params_.y);
107 position = [parent_view_ convertPoint:position toView:nil];
108 NSTimeInterval eventTime = [currentEvent timestamp];
109 NSEvent* clickEvent = [NSEvent mouseEventWithType:NSRightMouseDown
111 modifierFlags:NSRightMouseDownMask
113 windowNumber:[window windowNumber]
120 // Make sure events can be pumped while the menu is up.
121 base::MessageLoop::ScopedNestableTaskAllower allow(
122 base::MessageLoop::current());
124 // One of the events that could be pumped is |window.close()|.
125 // User-initiated event-tracking loops protect against this by
126 // setting flags in -[CrApplication sendEvent:], but since
127 // web-content menus are initiated by IPC message the setup has to
129 base::mac::ScopedSendingEvent sendingEventScoper;
132 [NSMenu popUpContextMenu:[menu_controller_ menu]
134 forView:parent_view_];
138 void RenderViewContextMenuMac::ExecuteCommand(int command_id, int event_flags) {
139 switch (command_id) {
140 case IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY:
141 LookUpInDictionary();
144 case IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING:
148 case IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING:
152 case IDC_WRITING_DIRECTION_DEFAULT:
153 // WebKit's current behavior is for this menu item to always be disabled.
157 case IDC_WRITING_DIRECTION_RTL:
158 case IDC_WRITING_DIRECTION_LTR: {
159 content::RenderViewHost* view_host = GetRenderViewHost();
160 blink::WebTextDirection dir = blink::WebTextDirectionLeftToRight;
161 if (command_id == IDC_WRITING_DIRECTION_RTL)
162 dir = blink::WebTextDirectionRightToLeft;
163 view_host->UpdateTextDirection(dir);
164 view_host->NotifyTextDirection();
169 RenderViewContextMenu::ExecuteCommand(command_id, event_flags);
174 bool RenderViewContextMenuMac::IsCommandIdChecked(int command_id) const {
175 switch (command_id) {
176 case IDC_WRITING_DIRECTION_DEFAULT:
177 return params_.writing_direction_default &
178 blink::WebContextMenuData::CheckableMenuItemChecked;
179 case IDC_WRITING_DIRECTION_RTL:
180 return params_.writing_direction_right_to_left &
181 blink::WebContextMenuData::CheckableMenuItemChecked;
182 case IDC_WRITING_DIRECTION_LTR:
183 return params_.writing_direction_left_to_right &
184 blink::WebContextMenuData::CheckableMenuItemChecked;
187 return RenderViewContextMenu::IsCommandIdChecked(command_id);
191 bool RenderViewContextMenuMac::IsCommandIdEnabled(int command_id) const {
192 switch (command_id) {
193 case IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY:
194 // This is OK because the menu is not shown when it isn't
198 case IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING:
199 // This is OK because the menu is not shown when it isn't
203 case IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING: {
204 content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
205 return view && view->IsSpeaking();
208 case IDC_WRITING_DIRECTION_DEFAULT: // Provided to match OS defaults.
209 return params_.writing_direction_default &
210 blink::WebContextMenuData::CheckableMenuItemEnabled;
211 case IDC_WRITING_DIRECTION_RTL:
212 return params_.writing_direction_right_to_left &
213 blink::WebContextMenuData::CheckableMenuItemEnabled;
214 case IDC_WRITING_DIRECTION_LTR:
215 return params_.writing_direction_left_to_right &
216 blink::WebContextMenuData::CheckableMenuItemEnabled;
219 return RenderViewContextMenu::IsCommandIdEnabled(command_id);
223 bool RenderViewContextMenuMac::GetAcceleratorForCommandId(
225 ui::Accelerator* accelerator) {
229 void RenderViewContextMenuMac::AppendPlatformEditableItems() {
230 // OS X provides a contextual menu to set writing direction for BiDi
232 // This functionality is exposed as a keyboard shortcut on Windows & Linux.
236 void RenderViewContextMenuMac::InitToolkitMenu() {
237 bool has_selection = !params_.selection_text.empty();
240 menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
241 menu_model_.AddItemWithStringId(
242 IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY,
243 IDS_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY);
245 content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
246 if (view && view->SupportsSpeech()) {
247 speech_submenu_model_.AddItemWithStringId(
248 IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING,
249 IDS_SPEECH_START_SPEAKING_MAC);
250 speech_submenu_model_.AddItemWithStringId(
251 IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING,
252 IDS_SPEECH_STOP_SPEAKING_MAC);
253 menu_model_.AddSubMenu(
254 IDC_CONTENT_CONTEXT_SPEECH_MENU,
255 l10n_util::GetStringUTF16(IDS_SPEECH_MAC),
256 &speech_submenu_model_);
261 void RenderViewContextMenuMac::CancelToolkitMenu() {
262 [menu_controller_ cancel];
265 void RenderViewContextMenuMac::UpdateToolkitMenuItem(
269 const base::string16& title) {
270 NSMenuItem* item = GetMenuItemByID(&menu_model_, [menu_controller_ menu],
275 // Update the returned NSMenuItem directly so we can update it immediately.
276 [item setEnabled:enabled];
277 [item setTitle:base::SysUTF16ToNSString(title)];
278 [item setHidden:hidden];
279 [[item menu] itemChanged:item];
282 void RenderViewContextMenuMac::AppendBidiSubMenu() {
283 bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_DEFAULT,
284 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_DEFAULT));
285 bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_LTR,
286 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_LTR));
287 bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_RTL,
288 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_RTL));
290 menu_model_.AddSubMenu(
291 IDC_WRITING_DIRECTION_MENU,
292 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_MENU),
293 &bidi_submenu_model_);
296 void RenderViewContextMenuMac::LookUpInDictionary() {
297 content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
299 view->ShowDefinitionForSelection();
302 void RenderViewContextMenuMac::StartSpeaking() {
303 content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
305 view->SpeakSelection();
308 void RenderViewContextMenuMac::StopSpeaking() {
309 content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
311 view->StopSpeaking();