Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(476)

Side by Side Diff: chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac.mm

Issue 2164483006: [MacViews] Implemented text context menu (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Added test suite for TextServicesContextMenu Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu _mac.h" 5 #include "chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu _mac.h"
6 6
7 #include <utility> 7 #include <utility>
8 8
9 #include "base/compiler_specific.h" 9 #include "base/compiler_specific.h"
10 #include "base/mac/mac_util.h" 10 #include "base/mac/mac_util.h"
11 #import "base/mac/scoped_objc_class_swizzler.h" 11 #import "base/mac/scoped_objc_class_swizzler.h"
12 #import "base/mac/scoped_sending_event.h" 12 #import "base/mac/scoped_sending_event.h"
13 #include "base/macros.h" 13 #include "base/macros.h"
14 #include "base/message_loop/message_loop.h" 14 #include "base/message_loop/message_loop.h"
15 #include "base/strings/sys_string_conversions.h" 15 #include "base/strings/sys_string_conversions.h"
16 #include "base/tracked_objects.h" 16 #include "base/tracked_objects.h"
17 #include "chrome/app/chrome_command_ids.h" 17 #include "chrome/app/chrome_command_ids.h"
18 #import "chrome/browser/mac/nsprocessinfo_additions.h" 18 #import "chrome/browser/mac/nsprocessinfo_additions.h"
19 #include "chrome/grit/generated_resources.h" 19 #include "chrome/grit/generated_resources.h"
20 #include "content/public/browser/render_view_host.h" 20 #include "content/public/browser/render_view_host.h"
21 #include "content/public/browser/render_widget_host.h" 21 #include "content/public/browser/render_widget_host.h"
22 #include "content/public/browser/render_widget_host_view.h" 22 #include "content/public/browser/render_widget_host_view.h"
23 #import "ui/base/cocoa/menu_controller.h" 23 #import "ui/base/cocoa/menu_controller.h"
24 #include "ui/base/l10n/l10n_util.h" 24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/strings/grit/ui_strings.h"
25 26
26 using content::WebContents; 27 using content::WebContents;
27 28
28 namespace { 29 namespace {
29 30
30 IMP g_original_populatemenu_implementation = nullptr; 31 IMP g_original_populatemenu_implementation = nullptr;
31 32
32 // |g_filtered_entries_array| is only set during testing (see 33 // |g_filtered_entries_array| is only set during testing (see
33 // +[ChromeSwizzleServicesMenuUpdater storeFilteredEntriesForTestingInArray:]). 34 // +[ChromeSwizzleServicesMenuUpdater storeFilteredEntriesForTestingInArray:]).
34 // Otherwise it remains nil. 35 // Otherwise it remains nil.
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after
168 bool hidden, 169 bool hidden,
169 const base::string16& title) override { 170 const base::string16& title) override {
170 context_menu_->UpdateToolkitMenuItem( 171 context_menu_->UpdateToolkitMenuItem(
171 command_id, enabled, hidden, title); 172 command_id, enabled, hidden, title);
172 } 173 }
173 174
174 RenderViewContextMenuMac* context_menu_; 175 RenderViewContextMenuMac* context_menu_;
175 DISALLOW_COPY_AND_ASSIGN(ToolkitDelegateMac); 176 DISALLOW_COPY_AND_ASSIGN(ToolkitDelegateMac);
176 }; 177 };
177 178
178 // Obj-C bridge class that is the target of all items in the context menu.
179 // Relies on the tag being set to the command id.
180
181 RenderViewContextMenuMac::RenderViewContextMenuMac( 179 RenderViewContextMenuMac::RenderViewContextMenuMac(
182 content::RenderFrameHost* render_frame_host, 180 content::RenderFrameHost* render_frame_host,
183 const content::ContextMenuParams& params, 181 const content::ContextMenuParams& params,
184 NSView* parent_view) 182 NSView* parent_view)
185 : RenderViewContextMenu(render_frame_host, params), 183 : RenderViewContextMenu(render_frame_host, params),
186 speech_submenu_model_(this), 184 parent_view_(parent_view),
187 bidi_submenu_model_(this), 185 text_services_context_menu_(this) {
188 parent_view_(parent_view) {
189 std::unique_ptr<ToolkitDelegate> delegate(new ToolkitDelegateMac(this)); 186 std::unique_ptr<ToolkitDelegate> delegate(new ToolkitDelegateMac(this));
190 set_toolkit_delegate(std::move(delegate)); 187 set_toolkit_delegate(std::move(delegate));
191 } 188 }
192 189
193 RenderViewContextMenuMac::~RenderViewContextMenuMac() { 190 RenderViewContextMenuMac::~RenderViewContextMenuMac() {
194 } 191 }
195 192
193 bool RenderViewContextMenuMac::IsCommandIdChecked(int command_id) const {
194 if (command_id == IDC_CONTENT_CONTEXT_LOOK_UP)
195 return false;
196
197 return RenderViewContextMenu::IsCommandIdChecked(command_id);
198 }
199
200 bool RenderViewContextMenuMac::IsCommandIdEnabled(int command_id) const {
201 if (command_id == IDC_CONTENT_CONTEXT_LOOK_UP)
202 return true;
203
204 return RenderViewContextMenu::IsCommandIdEnabled(command_id);
205 }
206
207 void RenderViewContextMenuMac::ExecuteCommand(int command_id, int event_flags) {
208 if (command_id == IDC_CONTENT_CONTEXT_LOOK_UP)
209 LookUpInDictionary();
210 else
211 RenderViewContextMenu::ExecuteCommand(command_id, event_flags);
212 }
213
214 base::string16 RenderViewContextMenuMac::GetSelectedText() const {
215 return params_.selection_text;
216 }
217
218 bool RenderViewContextMenuMac::IsTextDirectionEnabled(
219 base::i18n::TextDirection direction) const {
220 return BiDiParamsForDirection(direction) &
221 blink::WebContextMenuData::CheckableMenuItemEnabled;
222 }
223
224 bool RenderViewContextMenuMac::IsTextDirectionChecked(
225 base::i18n::TextDirection direction) const {
226 return BiDiParamsForDirection(direction) &
227 blink::WebContextMenuData::CheckableMenuItemChecked;
228 }
229
230 void RenderViewContextMenuMac::UpdateTextDirection(
231 base::i18n::TextDirection direction) {
232 DCHECK_NE(direction, base::i18n::UNKNOWN_DIRECTION);
233
234 content::RenderViewHost* view_host = GetRenderViewHost();
Alexei Svitkine (slow) 2017/02/03 15:43:27 Nit: Move this above line 242 right before it's us
spqchan 2017/02/03 23:32:10 Done.
235 blink::WebTextDirection dir = blink::WebTextDirectionLeftToRight;
236 int command_id = IDC_WRITING_DIRECTION_LTR;
237 if (direction == base::i18n::RIGHT_TO_LEFT) {
238 dir = blink::WebTextDirectionRightToLeft;
239 command_id = IDC_WRITING_DIRECTION_RTL;
240 }
241
242 view_host->GetWidget()->UpdateTextDirection(dir);
243 view_host->GetWidget()->NotifyTextDirection();
244
245 RenderViewContextMenu::RecordUsedItem(command_id);
246 }
247
196 void RenderViewContextMenuMac::Show() { 248 void RenderViewContextMenuMac::Show() {
197 menu_controller_.reset( 249 menu_controller_.reset(
198 [[MenuController alloc] initWithModel:&menu_model_ 250 [[MenuController alloc] initWithModel:&menu_model_
199 useWithPopUpButtonCell:NO]); 251 useWithPopUpButtonCell:NO]);
200 252
201 gfx::Point params_position(params_.x, params_.y); 253 gfx::Point params_position(params_.x, params_.y);
202 params_position += RenderViewContextMenu::GetOffset(GetRenderFrameHost()); 254 params_position += RenderViewContextMenu::GetOffset(GetRenderFrameHost());
203 255
204 // Synthesize an event for the click, as there is no certainty that 256 // Synthesize an event for the click, as there is no certainty that
205 // [NSApp currentEvent] will return a valid event. 257 // [NSApp currentEvent] will return a valid event.
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
237 tracked_objects::TaskStopwatch stopwatch; 289 tracked_objects::TaskStopwatch stopwatch;
238 stopwatch.Start(); 290 stopwatch.Start();
239 // Show the menu. 291 // Show the menu.
240 [NSMenu popUpContextMenu:[menu_controller_ menu] 292 [NSMenu popUpContextMenu:[menu_controller_ menu]
241 withEvent:clickEvent 293 withEvent:clickEvent
242 forView:parent_view_]; 294 forView:parent_view_];
243 stopwatch.Stop(); 295 stopwatch.Stop();
244 } 296 }
245 } 297 }
246 298
247 void RenderViewContextMenuMac::ExecuteCommand(int command_id, int event_flags) {
248 switch (command_id) {
249 case IDC_CONTENT_CONTEXT_LOOK_UP:
250 LookUpInDictionary();
251 break;
252
253 case IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING:
254 StartSpeaking();
255 break;
256
257 case IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING:
258 StopSpeaking();
259 break;
260
261 case IDC_WRITING_DIRECTION_DEFAULT:
262 // WebKit's current behavior is for this menu item to always be disabled.
263 NOTREACHED();
264 break;
265
266 case IDC_WRITING_DIRECTION_RTL:
267 case IDC_WRITING_DIRECTION_LTR: {
268 content::RenderViewHost* view_host = GetRenderViewHost();
269 blink::WebTextDirection dir = blink::WebTextDirectionLeftToRight;
270 if (command_id == IDC_WRITING_DIRECTION_RTL)
271 dir = blink::WebTextDirectionRightToLeft;
272 view_host->GetWidget()->UpdateTextDirection(dir);
273 view_host->GetWidget()->NotifyTextDirection();
274 RenderViewContextMenu::RecordUsedItem(command_id);
275 break;
276 }
277
278 default:
279 RenderViewContextMenu::ExecuteCommand(command_id, event_flags);
280 break;
281 }
282 }
283
284 bool RenderViewContextMenuMac::IsCommandIdChecked(int command_id) const {
285 switch (command_id) {
286 case IDC_WRITING_DIRECTION_DEFAULT:
287 return params_.writing_direction_default &
288 blink::WebContextMenuData::CheckableMenuItemChecked;
289 case IDC_WRITING_DIRECTION_RTL:
290 return params_.writing_direction_right_to_left &
291 blink::WebContextMenuData::CheckableMenuItemChecked;
292 case IDC_WRITING_DIRECTION_LTR:
293 return params_.writing_direction_left_to_right &
294 blink::WebContextMenuData::CheckableMenuItemChecked;
295
296 default:
297 return RenderViewContextMenu::IsCommandIdChecked(command_id);
298 }
299 }
300
301 bool RenderViewContextMenuMac::IsCommandIdEnabled(int command_id) const {
302 switch (command_id) {
303 case IDC_CONTENT_CONTEXT_LOOK_UP:
304 // This is OK because the menu is not shown when it isn't
305 // appropriate.
306 return true;
307
308 case IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING:
309 // This is OK because the menu is not shown when it isn't
310 // appropriate.
311 return true;
312
313 case IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING: {
314 content::RenderWidgetHostView* view =
315 GetRenderViewHost()->GetWidget()->GetView();
316 return view && view->IsSpeaking();
317 }
318
319 case IDC_WRITING_DIRECTION_DEFAULT: // Provided to match OS defaults.
320 return params_.writing_direction_default &
321 blink::WebContextMenuData::CheckableMenuItemEnabled;
322 case IDC_WRITING_DIRECTION_RTL:
323 return params_.writing_direction_right_to_left &
324 blink::WebContextMenuData::CheckableMenuItemEnabled;
325 case IDC_WRITING_DIRECTION_LTR:
326 return params_.writing_direction_left_to_right &
327 blink::WebContextMenuData::CheckableMenuItemEnabled;
328
329 default:
330 return RenderViewContextMenu::IsCommandIdEnabled(command_id);
331 }
332 }
333
334 void RenderViewContextMenuMac::AppendPlatformEditableItems() {
335 // OS X provides a contextual menu to set writing direction for BiDi
336 // languages.
337 // This functionality is exposed as a keyboard shortcut on Windows & Linux.
338 AppendBidiSubMenu();
339 }
340
341 void RenderViewContextMenuMac::InitToolkitMenu() { 299 void RenderViewContextMenuMac::InitToolkitMenu() {
342 if (params_.selection_text.empty())
343 return;
344
345 if (params_.link_url.is_empty()) { 300 if (params_.link_url.is_empty()) {
346 // In case the user has selected a word that triggers spelling suggestions, 301 // In case the user has selected a word that triggers spelling suggestions,
347 // show the dictionary lookup under the group that contains the command to 302 // show the dictionary lookup under the group that contains the command to
348 // “Add to Dictionary.” 303 // “Add to Dictionary.”
349 int index = menu_model_.GetIndexOfCommandId( 304 int index =
350 IDC_SPELLCHECK_ADD_TO_DICTIONARY); 305 menu_model_.GetIndexOfCommandId(IDC_SPELLCHECK_ADD_TO_DICTIONARY);
351 if (index < 0) { 306 if (index < 0) {
352 index = 0; 307 index = 0;
353 } else { 308 } else {
354 while (menu_model_.GetTypeAt(index) != ui::MenuModel::TYPE_SEPARATOR) { 309 while (menu_model_.GetTypeAt(index) != ui::MenuModel::TYPE_SEPARATOR)
355 index++; 310 index++;
356 } 311
357 index += 1; // Place it below the separator. 312 index += 1; // Place it below the separator.
358 } 313 }
359 314
360 base::string16 printable_selection_text = PrintableSelectionText(); 315 base::string16 printable_selection_text = PrintableSelectionText();
361 EscapeAmpersands(&printable_selection_text); 316 EscapeAmpersands(&printable_selection_text);
362 menu_model_.InsertItemAt( 317 menu_model_.InsertItemAt(
363 index++, 318 index++, IDC_CONTENT_CONTEXT_LOOK_UP,
364 IDC_CONTENT_CONTEXT_LOOK_UP,
365 l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, 319 l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP,
366 printable_selection_text)); 320 printable_selection_text));
367 menu_model_.InsertSeparatorAt(index++, ui::NORMAL_SEPARATOR); 321 menu_model_.InsertSeparatorAt(index, ui::NORMAL_SEPARATOR);
368 } 322 }
369 323
370 content::RenderWidgetHostView* view = 324 text_services_context_menu_.AppendToContextMenu(&menu_model_);
371 GetRenderViewHost()->GetWidget()->GetView();
372 if (view && view->SupportsSpeech()) {
373 menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
374 speech_submenu_model_.AddItemWithStringId(
375 IDC_CONTENT_CONTEXT_SPEECH_START_SPEAKING,
376 IDS_SPEECH_START_SPEAKING_MAC);
377 speech_submenu_model_.AddItemWithStringId(
378 IDC_CONTENT_CONTEXT_SPEECH_STOP_SPEAKING,
379 IDS_SPEECH_STOP_SPEAKING_MAC);
380 menu_model_.AddSubMenu(
381 IDC_CONTENT_CONTEXT_SPEECH_MENU,
382 l10n_util::GetStringUTF16(IDS_SPEECH_MAC),
383 &speech_submenu_model_);
384 }
385 } 325 }
386 326
387 void RenderViewContextMenuMac::CancelToolkitMenu() { 327 void RenderViewContextMenuMac::CancelToolkitMenu() {
388 [menu_controller_ cancel]; 328 [menu_controller_ cancel];
389 } 329 }
390 330
391 void RenderViewContextMenuMac::UpdateToolkitMenuItem( 331 void RenderViewContextMenuMac::UpdateToolkitMenuItem(
392 int command_id, 332 int command_id,
393 bool enabled, 333 bool enabled,
394 bool hidden, 334 bool hidden,
395 const base::string16& title) { 335 const base::string16& title) {
396 NSMenuItem* item = GetMenuItemByID(&menu_model_, [menu_controller_ menu], 336 NSMenuItem* item = GetMenuItemByID(&menu_model_, [menu_controller_ menu],
397 command_id); 337 command_id);
398 if (!item) 338 if (!item)
399 return; 339 return;
400 340
401 // Update the returned NSMenuItem directly so we can update it immediately. 341 // Update the returned NSMenuItem directly so we can update it immediately.
402 [item setEnabled:enabled]; 342 [item setEnabled:enabled];
403 [item setTitle:base::SysUTF16ToNSString(title)]; 343 [item setTitle:base::SysUTF16ToNSString(title)];
404 [item setHidden:hidden]; 344 [item setHidden:hidden];
405 [[item menu] itemChanged:item]; 345 [[item menu] itemChanged:item];
406 } 346 }
407 347
408 void RenderViewContextMenuMac::AppendBidiSubMenu() {
409 bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_DEFAULT,
410 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_DEFAULT));
411 bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_LTR,
412 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_LTR));
413 bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_RTL,
414 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_RTL));
415
416 menu_model_.AddSubMenu(
417 IDC_WRITING_DIRECTION_MENU,
418 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_MENU),
419 &bidi_submenu_model_);
420 }
421
422 void RenderViewContextMenuMac::LookUpInDictionary() { 348 void RenderViewContextMenuMac::LookUpInDictionary() {
423 content::RenderWidgetHostView* view = 349 content::RenderWidgetHostView* view =
424 GetRenderViewHost()->GetWidget()->GetView(); 350 GetRenderViewHost()->GetWidget()->GetView();
425 if (view) 351 if (view)
426 view->ShowDefinitionForSelection(); 352 view->ShowDefinitionForSelection();
427 } 353 }
428 354
429 void RenderViewContextMenuMac::StartSpeaking() { 355 int RenderViewContextMenuMac::BiDiParamsForDirection(
430 content::RenderWidgetHostView* view = 356 base::i18n::TextDirection direction) const {
431 GetRenderViewHost()->GetWidget()->GetView(); 357 switch (direction) {
432 if (view) 358 case base::i18n::UNKNOWN_DIRECTION:
433 view->SpeakSelection(); 359 return params_.writing_direction_default;
360 case base::i18n::RIGHT_TO_LEFT:
361 return params_.writing_direction_right_to_left;
362 case base::i18n::LEFT_TO_RIGHT:
363 return params_.writing_direction_left_to_right;
364 }
434 } 365 }
435
436 void RenderViewContextMenuMac::StopSpeaking() {
437 content::RenderWidgetHostView* view =
438 GetRenderViewHost()->GetWidget()->GetView();
439 if (view)
440 view->StopSpeaking();
441 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698