Upstream version 10.39.225.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 "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"
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 // 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 {
53  public:
54   explicit ToolkitDelegateMac(RenderViewContextMenuMac* context_menu)
55       : context_menu_(context_menu) {}
56   virtual ~ToolkitDelegateMac() {}
57
58  private:
59   // ToolkitDelegate:
60   virtual void Init(ui::SimpleMenuModel* menu_model) OVERRIDE {
61     context_menu_->InitToolkitMenu();
62   }
63   virtual void Cancel() OVERRIDE{
64     context_menu_->CancelToolkitMenu();
65   }
66   virtual void UpdateMenuItem(int command_id,
67                               bool enabled,
68                               bool hidden,
69                               const base::string16& title) OVERRIDE {
70     context_menu_->UpdateToolkitMenuItem(
71         command_id, enabled, hidden, title);
72   }
73
74   RenderViewContextMenuMac* context_menu_;
75   DISALLOW_COPY_AND_ASSIGN(ToolkitDelegateMac);
76 };
77
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.
80
81 RenderViewContextMenuMac::RenderViewContextMenuMac(
82     content::RenderFrameHost* render_frame_host,
83     const content::ContextMenuParams& params,
84     NSView* parent_view)
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());
91 }
92
93 RenderViewContextMenuMac::~RenderViewContextMenuMac() {
94 }
95
96 void RenderViewContextMenuMac::Show() {
97   menu_controller_.reset(
98       [[MenuController alloc] initWithModel:&menu_model_
99                      useWithPopUpButtonCell:NO]);
100
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];
105   NSPoint position =
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
110                                            location:position
111                                       modifierFlags:NSRightMouseDownMask
112                                           timestamp:eventTime
113                                        windowNumber:[window windowNumber]
114                                             context:nil
115                                         eventNumber:0
116                                          clickCount:1
117                                            pressure:1.0];
118
119   {
120     // Make sure events can be pumped while the menu is up.
121     base::MessageLoop::ScopedNestableTaskAllower allow(
122         base::MessageLoop::current());
123
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
128     // be done manually.
129     base::mac::ScopedSendingEvent sendingEventScoper;
130
131     // Show the menu.
132     [NSMenu popUpContextMenu:[menu_controller_ menu]
133                    withEvent:clickEvent
134                      forView:parent_view_];
135   }
136 }
137
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();
142       break;
143
144     case IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING:
145       StartSpeaking();
146       break;
147
148     case IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING:
149       StopSpeaking();
150       break;
151
152     case IDC_WRITING_DIRECTION_DEFAULT:
153       // WebKit's current behavior is for this menu item to always be disabled.
154       NOTREACHED();
155       break;
156
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();
165       break;
166     }
167
168     default:
169       RenderViewContextMenu::ExecuteCommand(command_id, event_flags);
170       break;
171   }
172 }
173
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;
185
186     default:
187       return RenderViewContextMenu::IsCommandIdChecked(command_id);
188   }
189 }
190
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
195       // appropriate.
196       return true;
197
198     case IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING:
199       // This is OK because the menu is not shown when it isn't
200       // appropriate.
201       return true;
202
203     case IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING: {
204       content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
205       return view && view->IsSpeaking();
206     }
207
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;
217
218     default:
219       return RenderViewContextMenu::IsCommandIdEnabled(command_id);
220   }
221 }
222
223 bool RenderViewContextMenuMac::GetAcceleratorForCommandId(
224     int command_id,
225     ui::Accelerator* accelerator) {
226   return false;
227 }
228
229 void RenderViewContextMenuMac::AppendPlatformEditableItems() {
230   // OS X provides a contextual menu to set writing direction for BiDi
231   // languages.
232   // This functionality is exposed as a keyboard shortcut on Windows & Linux.
233   AppendBidiSubMenu();
234 }
235
236 void RenderViewContextMenuMac::InitToolkitMenu() {
237   bool has_selection = !params_.selection_text.empty();
238
239   if (has_selection) {
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);
244
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_);
257     }
258   }
259 }
260
261 void RenderViewContextMenuMac::CancelToolkitMenu() {
262   [menu_controller_ cancel];
263 }
264
265 void RenderViewContextMenuMac::UpdateToolkitMenuItem(
266     int command_id,
267     bool enabled,
268     bool hidden,
269     const base::string16& title) {
270   NSMenuItem* item = GetMenuItemByID(&menu_model_, [menu_controller_ menu],
271                                      command_id);
272   if (!item)
273     return;
274
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];
280 }
281
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));
289
290   menu_model_.AddSubMenu(
291       IDC_WRITING_DIRECTION_MENU,
292       l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_MENU),
293       &bidi_submenu_model_);
294 }
295
296 void RenderViewContextMenuMac::LookUpInDictionary() {
297   content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
298   if (view)
299     view->ShowDefinitionForSelection();
300 }
301
302 void RenderViewContextMenuMac::StartSpeaking() {
303   content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
304   if (view)
305     view->SpeakSelection();
306 }
307
308 void RenderViewContextMenuMac::StopSpeaking() {
309   content::RenderWidgetHostView* view = GetRenderViewHost()->GetView();
310   if (view)
311     view->StopSpeaking();
312 }