Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / renderer_context_menu / render_view_context_menu_mac.mm
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.
4
5 #include "chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac.h"
6
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 "content/public/browser/render_view_host.h"
13 #include "content/public/browser/render_widget_host_view.h"
14 #include "grit/generated_resources.h"
15 #import "ui/base/cocoa/menu_controller.h"
16 #include "ui/base/l10n/l10n_util.h"
17
18 using content::WebContents;
19
20 namespace {
21
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,
28                             NSMenu* menu,
29                             int command_id) {
30   for (int i = 0; i < model->GetItemCount(); ++i) {
31     NSMenuItem* item = [menu itemAtIndex:i];
32     if (model->GetCommandIdAt(i) == command_id)
33       return item;
34
35     ui::MenuModel* submenu = model->GetSubmenuModelAt(i);
36     if (submenu && [item hasSubmenu]) {
37       NSMenuItem* subitem = GetMenuItemByID(submenu,
38                                             [item submenu],
39                                             command_id);
40       if (subitem)
41         return subitem;
42     }
43   }
44   return nil;
45 }
46
47 }  // namespace
48
49 // Obj-C bridge class that is the target of all items in the context menu.
50 // Relies on the tag being set to the command id.
51
52 RenderViewContextMenuMac::RenderViewContextMenuMac(
53     content::RenderFrameHost* render_frame_host,
54     const content::ContextMenuParams& params,
55     NSView* parent_view)
56     : RenderViewContextMenu(render_frame_host, params),
57       speech_submenu_model_(this),
58       bidi_submenu_model_(this),
59       parent_view_(parent_view) {
60 }
61
62 RenderViewContextMenuMac::~RenderViewContextMenuMac() {
63 }
64
65 void RenderViewContextMenuMac::PlatformInit() {
66   InitPlatformMenu();
67 }
68
69 void RenderViewContextMenuMac::Show() {
70   menu_controller_.reset(
71       [[MenuController alloc] initWithModel:&menu_model_
72                      useWithPopUpButtonCell:NO]);
73
74   // Synthesize an event for the click, as there is no certainty that
75   // [NSApp currentEvent] will return a valid event.
76   NSEvent* currentEvent = [NSApp currentEvent];
77   NSWindow* window = [parent_view_ window];
78   NSPoint position = [window mouseLocationOutsideOfEventStream];
79   NSTimeInterval eventTime = [currentEvent timestamp];
80   NSEvent* clickEvent = [NSEvent mouseEventWithType:NSRightMouseDown
81                                            location:position
82                                       modifierFlags:NSRightMouseDownMask
83                                           timestamp:eventTime
84                                        windowNumber:[window windowNumber]
85                                             context:nil
86                                         eventNumber:0
87                                          clickCount:1
88                                            pressure:1.0];
89
90   {
91     // Make sure events can be pumped while the menu is up.
92     base::MessageLoop::ScopedNestableTaskAllower allow(
93         base::MessageLoop::current());
94
95     // One of the events that could be pumped is |window.close()|.
96     // User-initiated event-tracking loops protect against this by
97     // setting flags in -[CrApplication sendEvent:], but since
98     // web-content menus are initiated by IPC message the setup has to
99     // be done manually.
100     base::mac::ScopedSendingEvent sendingEventScoper;
101
102     // Show the menu.
103     [NSMenu popUpContextMenu:[menu_controller_ menu]
104                    withEvent:clickEvent
105                      forView:parent_view_];
106   }
107 }
108
109 void RenderViewContextMenuMac::PlatformCancel() {
110   [menu_controller_ cancel];
111 }
112
113 void RenderViewContextMenuMac::ExecuteCommand(int command_id, int event_flags) {
114   switch (command_id) {
115     case IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY:
116       LookUpInDictionary();
117       break;
118
119     case IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING:
120       StartSpeaking();
121       break;
122
123     case IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING:
124       StopSpeaking();
125       break;
126
127     case IDC_WRITING_DIRECTION_DEFAULT:
128       // WebKit's current behavior is for this menu item to always be disabled.
129       NOTREACHED();
130       break;
131
132     case IDC_WRITING_DIRECTION_RTL:
133     case IDC_WRITING_DIRECTION_LTR: {
134       content::RenderViewHost* view_host = GetRenderViewHost();
135       blink::WebTextDirection dir = blink::WebTextDirectionLeftToRight;
136       if (command_id == IDC_WRITING_DIRECTION_RTL)
137         dir = blink::WebTextDirectionRightToLeft;
138       view_host->UpdateTextDirection(dir);
139       view_host->NotifyTextDirection();
140       break;
141     }
142
143     default:
144       RenderViewContextMenu::ExecuteCommand(command_id, event_flags);
145       break;
146   }
147 }
148
149 bool RenderViewContextMenuMac::IsCommandIdChecked(int command_id) const {
150   switch (command_id) {
151     case IDC_WRITING_DIRECTION_DEFAULT:
152       return params_.writing_direction_default &
153           blink::WebContextMenuData::CheckableMenuItemChecked;
154     case IDC_WRITING_DIRECTION_RTL:
155       return params_.writing_direction_right_to_left &
156           blink::WebContextMenuData::CheckableMenuItemChecked;
157     case IDC_WRITING_DIRECTION_LTR:
158       return params_.writing_direction_left_to_right &
159           blink::WebContextMenuData::CheckableMenuItemChecked;
160
161     default:
162       return RenderViewContextMenu::IsCommandIdChecked(command_id);
163   }
164 }
165
166 bool RenderViewContextMenuMac::IsCommandIdEnabled(int command_id) const {
167   switch (command_id) {
168     case IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY:
169       // This is OK because the menu is not shown when it isn't
170       // appropriate.
171       return true;
172
173     case IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING:
174       // This is OK because the menu is not shown when it isn't
175       // appropriate.
176       return true;
177
178     case IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING: {
179       content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
180       return view && view->IsSpeaking();
181     }
182
183     case IDC_WRITING_DIRECTION_DEFAULT:  // Provided to match OS defaults.
184       return params_.writing_direction_default &
185           blink::WebContextMenuData::CheckableMenuItemEnabled;
186     case IDC_WRITING_DIRECTION_RTL:
187       return params_.writing_direction_right_to_left &
188           blink::WebContextMenuData::CheckableMenuItemEnabled;
189     case IDC_WRITING_DIRECTION_LTR:
190       return params_.writing_direction_left_to_right &
191           blink::WebContextMenuData::CheckableMenuItemEnabled;
192
193     default:
194       return RenderViewContextMenu::IsCommandIdEnabled(command_id);
195   }
196 }
197
198 bool RenderViewContextMenuMac::GetAcceleratorForCommandId(
199     int command_id,
200     ui::Accelerator* accelerator) {
201   return false;
202 }
203
204 void RenderViewContextMenuMac::AppendPlatformEditableItems() {
205   // OS X provides a contextual menu to set writing direction for BiDi
206   // languages.
207   // This functionality is exposed as a keyboard shortcut on Windows & Linux.
208   AppendBidiSubMenu();
209 }
210
211 void RenderViewContextMenuMac::InitPlatformMenu() {
212   bool has_selection = !params_.selection_text.empty();
213
214   if (has_selection) {
215     menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
216     menu_model_.AddItemWithStringId(
217         IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY,
218         IDS_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY);
219
220     content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
221     if (view && view->SupportsSpeech()) {
222       speech_submenu_model_.AddItemWithStringId(
223           IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING,
224           IDS_SPEECH_START_SPEAKING_MAC);
225       speech_submenu_model_.AddItemWithStringId(
226           IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING,
227           IDS_SPEECH_STOP_SPEAKING_MAC);
228       menu_model_.AddSubMenu(
229           IDC_CONTENT_CONTEXT_SPEECH_MENU,
230           l10n_util::GetStringUTF16(IDS_SPEECH_MAC),
231           &speech_submenu_model_);
232     }
233   }
234 }
235
236 void RenderViewContextMenuMac::AppendBidiSubMenu() {
237   bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_DEFAULT,
238       l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_DEFAULT));
239   bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_LTR,
240       l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_LTR));
241   bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_RTL,
242       l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_RTL));
243
244   menu_model_.AddSubMenu(
245       IDC_WRITING_DIRECTION_MENU,
246       l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_MENU),
247       &bidi_submenu_model_);
248 }
249
250 void RenderViewContextMenuMac::LookUpInDictionary() {
251   content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
252   if (view)
253     view->ShowDefinitionForSelection();
254 }
255
256 void RenderViewContextMenuMac::StartSpeaking() {
257   content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
258   if (view)
259     view->SpeakSelection();
260 }
261
262 void RenderViewContextMenuMac::StopSpeaking() {
263   content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
264   if (view)
265     view->StopSpeaking();
266 }
267
268 void RenderViewContextMenuMac::UpdateMenuItem(int command_id,
269                                               bool enabled,
270                                               bool hidden,
271                                               const base::string16& title) {
272   NSMenuItem* item = GetMenuItemByID(&menu_model_, [menu_controller_ menu],
273                                      command_id);
274   if (!item)
275     return;
276
277   // Update the returned NSMenuItem directly so we can update it immediately.
278   [item setEnabled:enabled];
279   [item setTitle:SysUTF16ToNSString(title)];
280   [item setHidden:hidden];
281   [[item menu] itemChanged:item];
282 }