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