omnibox_view_views.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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/views/omnibox/omnibox_view_views.h" 6 7#include "base/logging.h" 8#include "base/string_util.h" 9#include "base/utf_string_conversions.h" 10#include "chrome/app/chrome_command_ids.h" 11#include "chrome/browser/autocomplete/autocomplete_input.h" 12#include "chrome/browser/autocomplete/autocomplete_match.h" 13#include "chrome/browser/bookmarks/bookmark_node_data.h" 14#include "chrome/browser/command_updater.h" 15#include "chrome/browser/profiles/profile.h" 16#include "chrome/browser/search/search.h" 17#include "chrome/browser/ui/omnibox/omnibox_edit_controller.h" 18#include "chrome/browser/ui/omnibox/omnibox_edit_model.h" 19#include "chrome/browser/ui/omnibox/omnibox_popup_model.h" 20#include "chrome/browser/ui/view_ids.h" 21#include "chrome/browser/ui/views/location_bar/location_bar_view.h" 22#include "chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h" 23#include "content/public/browser/web_contents.h" 24#include "extensions/common/constants.h" 25#include "googleurl/src/gurl.h" 26#include "grit/app_locale_settings.h" 27#include "grit/generated_resources.h" 28#include "grit/ui_strings.h" 29#include "net/base/escape.h" 30#include "third_party/skia/include/core/SkColor.h" 31#include "ui/base/accessibility/accessible_view_state.h" 32#include "ui/base/clipboard/scoped_clipboard_writer.h" 33#include "ui/base/dragdrop/drag_drop_types.h" 34#include "ui/base/dragdrop/os_exchange_data.h" 35#include "ui/base/events/event.h" 36#include "ui/base/ime/text_input_client.h" 37#include "ui/base/ime/text_input_type.h" 38#include "ui/base/l10n/l10n_util.h" 39#include "ui/base/models/simple_menu_model.h" 40#include "ui/base/resource/resource_bundle.h" 41#include "ui/gfx/canvas.h" 42#include "ui/gfx/font.h" 43#include "ui/gfx/selection_model.h" 44#include "ui/views/border.h" 45#include "ui/views/button_drag_utils.h" 46#include "ui/views/controls/textfield/textfield.h" 47#include "ui/views/ime/input_method.h" 48#include "ui/views/layout/fill_layout.h" 49#include "ui/views/views_delegate.h" 50#include "ui/views/widget/widget.h" 51 52#if defined(USE_AURA) 53#include "ui/aura/focus_manager.h" 54#include "ui/aura/root_window.h" 55#include "ui/compositor/layer.h" 56#endif 57 58namespace { 59 60// Stores omnibox state for each tab. 61struct OmniboxState : public base::SupportsUserData::Data { 62 static const char kKey[]; 63 64 OmniboxState(const OmniboxEditModel::State& model_state, 65 const gfx::SelectionModel& selection_model); 66 virtual ~OmniboxState(); 67 68 const OmniboxEditModel::State model_state; 69 const gfx::SelectionModel selection_model; 70}; 71 72// static 73const char OmniboxState::kKey[] = "OmniboxState"; 74 75OmniboxState::OmniboxState(const OmniboxEditModel::State& model_state, 76 const gfx::SelectionModel& selection_model) 77 : model_state(model_state), 78 selection_model(selection_model) { 79} 80 81OmniboxState::~OmniboxState() {} 82 83// This will write |url| and |text| to the clipboard as a well-formed URL. 84void DoCopyURL(const GURL& url, const string16& text, Profile* profile) { 85 BookmarkNodeData data; 86 data.ReadFromTuple(url, text); 87 data.WriteToClipboard(profile); 88} 89 90} // namespace 91 92// static 93const char OmniboxViewViews::kViewClassName[] = "OmniboxViewViews"; 94 95OmniboxViewViews::OmniboxViewViews(OmniboxEditController* controller, 96 ToolbarModel* toolbar_model, 97 Profile* profile, 98 CommandUpdater* command_updater, 99 bool popup_window_mode, 100 LocationBarView* location_bar, 101 const gfx::Font& font, 102 int font_y_offset) 103 : OmniboxView(profile, controller, toolbar_model, command_updater), 104 popup_window_mode_(popup_window_mode), 105 security_level_(ToolbarModel::NONE), 106 ime_composing_before_change_(false), 107 delete_at_end_pressed_(false), 108 location_bar_view_(location_bar), 109 ime_candidate_window_open_(false), 110 select_all_on_mouse_release_(false), 111 select_all_on_gesture_tap_(false) { 112 RemoveBorder(); 113 set_id(VIEW_ID_OMNIBOX); 114 SetFont(font); 115 SetVerticalMargins(font_y_offset, 0); 116 SetVerticalAlignment(gfx::ALIGN_TOP); 117} 118 119OmniboxViewViews::~OmniboxViewViews() { 120#if defined(OS_CHROMEOS) 121 chromeos::input_method::InputMethodManager::Get()-> 122 RemoveCandidateWindowObserver(this); 123#endif 124 125 // Explicitly teardown members which have a reference to us. Just to be safe 126 // we want them to be destroyed before destroying any other internal state. 127 popup_view_.reset(); 128} 129 130//////////////////////////////////////////////////////////////////////////////// 131// OmniboxViewViews public: 132 133void OmniboxViewViews::Init() { 134 SetController(this); 135 SetTextInputType(ui::TEXT_INPUT_TYPE_URL); 136 SetBackgroundColor(location_bar_view_->GetColor( 137 ToolbarModel::NONE, LocationBarView::BACKGROUND)); 138 139 if (popup_window_mode_) 140 SetReadOnly(true); 141 142 // Initialize the popup view using the same font. 143 popup_view_.reset(OmniboxPopupContentsView::Create( 144 font(), this, model(), location_bar_view_)); 145 146#if defined(OS_CHROMEOS) 147 chromeos::input_method::InputMethodManager::Get()-> 148 AddCandidateWindowObserver(this); 149#endif 150} 151 152//////////////////////////////////////////////////////////////////////////////// 153// OmniboxViewViews, views::Textfield implementation: 154 155const char* OmniboxViewViews::GetClassName() const { 156 return kViewClassName; 157} 158 159void OmniboxViewViews::OnGestureEvent(ui::GestureEvent* event) { 160 views::Textfield::OnGestureEvent(event); 161 if (!HasFocus() && event->type() == ui::ET_GESTURE_TAP_DOWN) { 162 select_all_on_gesture_tap_ = true; 163 return; 164 } 165 if (select_all_on_gesture_tap_ && event->type() == ui::ET_GESTURE_TAP) 166 SelectAll(false); 167 select_all_on_gesture_tap_ = false; 168} 169 170void OmniboxViewViews::GetAccessibleState(ui::AccessibleViewState* state) { 171 location_bar_view_->GetAccessibleState(state); 172 state->role = ui::AccessibilityTypes::ROLE_TEXT; 173} 174 175bool OmniboxViewViews::OnMousePressed(const ui::MouseEvent& event) { 176 select_all_on_mouse_release_ = 177 (event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton()) && 178 (!HasFocus() || (model()->focus_state() == OMNIBOX_FOCUS_INVISIBLE)); 179 // Restore caret visibility whenever the user clicks in the omnibox in a way 180 // that would give it focus. We must handle this case separately here because 181 // if the omnibox currently has invisible focus, the mouse event won't trigger 182 // either SetFocus() or OmniboxEditModel::OnSetFocus(). 183 if (select_all_on_mouse_release_) 184 model()->SetCaretVisibility(true); 185 return views::Textfield::OnMousePressed(event); 186} 187 188bool OmniboxViewViews::OnMouseDragged(const ui::MouseEvent& event) { 189 select_all_on_mouse_release_ = false; 190 return views::Textfield::OnMouseDragged(event); 191} 192 193void OmniboxViewViews::OnMouseReleased(const ui::MouseEvent& event) { 194 views::Textfield::OnMouseReleased(event); 195 if ((event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton()) && 196 select_all_on_mouse_release_) { 197 // Select all in the reverse direction so as not to scroll the caret 198 // into view and shift the contents jarringly. 199 SelectAll(true); 200 } 201 select_all_on_mouse_release_ = false; 202} 203 204bool OmniboxViewViews::OnKeyPressed(const ui::KeyEvent& event) { 205 // Skip processing of [Alt]+<num-pad digit> Unicode alt key codes. 206 // Otherwise, if num-lock is off, the events are handled as [Up], [Down], etc. 207 if (event.IsUnicodeKeyCode()) 208 return views::Textfield::OnKeyPressed(event); 209 210 switch (event.key_code()) { 211 case ui::VKEY_RETURN: 212 model()->AcceptInput(event.IsAltDown() ? NEW_FOREGROUND_TAB : CURRENT_TAB, 213 false); 214 return true; 215 case ui::VKEY_ESCAPE: 216 return model()->OnEscapeKeyPressed(); 217 case ui::VKEY_CONTROL: 218 model()->OnControlKeyChanged(true); 219 break; 220 case ui::VKEY_DELETE: 221 if (event.IsShiftDown() && model()->popup_model()->IsOpen()) 222 model()->popup_model()->TryDeletingCurrentItem(); 223 break; 224 case ui::VKEY_UP: 225 model()->OnUpOrDownKeyPressed(-1); 226 return true; 227 case ui::VKEY_DOWN: 228 model()->OnUpOrDownKeyPressed(1); 229 return true; 230 case ui::VKEY_PRIOR: 231 if (event.IsControlDown() || event.IsAltDown() || 232 event.IsShiftDown()) { 233 return false; 234 } 235 model()->OnUpOrDownKeyPressed(-1 * model()->result().size()); 236 return true; 237 case ui::VKEY_NEXT: 238 if (event.IsControlDown() || event.IsAltDown() || 239 event.IsShiftDown()) { 240 return false; 241 } 242 model()->OnUpOrDownKeyPressed(model()->result().size()); 243 return true; 244 case ui::VKEY_V: 245 if (event.IsControlDown() && !read_only()) { 246 OnBeforePossibleChange(); 247 OnPaste(); 248 OnAfterPossibleChange(); 249 return true; 250 } 251 break; 252 default: 253 break; 254 } 255 256 bool handled = views::Textfield::OnKeyPressed(event); 257#if !defined(OS_WIN) || defined(USE_AURA) 258 // TODO(msw): Avoid this complexity, consolidate cross-platform behavior. 259 handled |= SkipDefaultKeyEventProcessing(event); 260#endif 261 return handled; 262} 263 264bool OmniboxViewViews::OnKeyReleased(const ui::KeyEvent& event) { 265 // The omnibox contents may change while the control key is pressed. 266 if (event.key_code() == ui::VKEY_CONTROL) 267 model()->OnControlKeyChanged(false); 268 return views::Textfield::OnKeyReleased(event); 269} 270 271bool OmniboxViewViews::SkipDefaultKeyEventProcessing( 272 const ui::KeyEvent& event) { 273 // Handle keyword hint tab-to-search and tabbing through dropdown results. 274 // This must run before acclerator handling invokes a focus change on tab. 275 if (views::FocusManager::IsTabTraversalKeyEvent(event)) { 276 if (model()->is_keyword_hint() && !event.IsShiftDown()) { 277 model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB); 278 return true; 279 } 280 if (model()->popup_model()->IsOpen()) { 281 if (event.IsShiftDown() && 282 model()->popup_model()->selected_line_state() == 283 OmniboxPopupModel::KEYWORD) { 284 model()->ClearKeyword(text()); 285 } else { 286 model()->OnUpOrDownKeyPressed(event.IsShiftDown() ? -1 : 1); 287 } 288 return true; 289 } 290 } 291 292 return Textfield::SkipDefaultKeyEventProcessing(event); 293} 294 295void OmniboxViewViews::OnFocus() { 296 views::Textfield::OnFocus(); 297 // TODO(oshima): Get control key state. 298 model()->OnSetFocus(false); 299 // Don't call controller()->OnSetFocus, this view has already acquired focus. 300 301 // Restore a valid saved selection on tab-to-focus. 302 if (location_bar_view_->GetWebContents() && !select_all_on_mouse_release_) { 303 const OmniboxState* state = static_cast<OmniboxState*>( 304 location_bar_view_->GetWebContents()->GetUserData(&OmniboxState::kKey)); 305 if (state) 306 SelectSelectionModel(state->selection_model); 307 } 308} 309 310void OmniboxViewViews::OnBlur() { 311 // Save the selection to restore on tab-to-focus. 312 if (location_bar_view_->GetWebContents()) 313 SaveStateToTab(location_bar_view_->GetWebContents()); 314 315 views::Textfield::OnBlur(); 316 gfx::NativeView native_view = NULL; 317#if defined(USE_AURA) 318 views::Widget* widget = GetWidget(); 319 if (widget) { 320 aura::client::FocusClient* client = 321 aura::client::GetFocusClient(widget->GetNativeView()); 322 if (client) 323 native_view = client->GetFocusedWindow(); 324 } 325#endif 326 model()->OnWillKillFocus(native_view); 327 // Close the popup. 328 CloseOmniboxPopup(); 329 // Tell the model to reset itself. 330 model()->OnKillFocus(); 331 controller()->OnKillFocus(); 332 333 // Make sure the beginning of the text is visible. 334 SelectRange(ui::Range(0)); 335} 336 337//////////////////////////////////////////////////////////////////////////////// 338// OmniboxViewViews, OmniboxView implementation: 339 340void OmniboxViewViews::SaveStateToTab(content::WebContents* tab) { 341 DCHECK(tab); 342 343 // We don't want to keep the IME status, so force quit the current 344 // session here. It may affect the selection status, so order is 345 // also important. 346 if (IsIMEComposing()) { 347 GetTextInputClient()->ConfirmCompositionText(); 348 GetInputMethod()->CancelComposition(this); 349 } 350 351 // NOTE: GetStateForTabSwitch may affect GetSelection, so order is important. 352 OmniboxEditModel::State state = model()->GetStateForTabSwitch(); 353 const gfx::SelectionModel selection = GetSelectionModel(); 354 tab->SetUserData(OmniboxState::kKey, new OmniboxState(state, selection)); 355} 356 357void OmniboxViewViews::Update(const content::WebContents* contents) { 358 // NOTE: We're getting the URL text here from the ToolbarModel. 359 bool visibly_changed_permanent_text = 360 model()->UpdatePermanentText(toolbar_model()->GetText(true)); 361 ToolbarModel::SecurityLevel security_level = 362 toolbar_model()->GetSecurityLevel(); 363 bool changed_security_level = (security_level != security_level_); 364 security_level_ = security_level; 365 366 // TODO(msw|oshima): Copied from GTK, determine correct Win/CrOS behavior. 367 if (contents) { 368 RevertAll(); 369 const OmniboxState* state = static_cast<OmniboxState*>( 370 contents->GetUserData(&OmniboxState::kKey)); 371 if (state) { 372 // Restore the saved state and selection. 373 model()->RestoreState(state->model_state); 374 SelectSelectionModel(state->selection_model); 375 // TODO(msw|oshima): Consider saving/restoring edit history. 376 ClearEditHistory(); 377 } 378 } else if (visibly_changed_permanent_text) { 379 RevertAll(); 380 } else if (changed_security_level) { 381 EmphasizeURLComponents(); 382 } 383} 384 385string16 OmniboxViewViews::GetText() const { 386 // TODO(oshima): IME support 387 return text(); 388} 389 390void OmniboxViewViews::SetWindowTextAndCaretPos(const string16& text, 391 size_t caret_pos, 392 bool update_popup, 393 bool notify_text_changed) { 394 const ui::Range range(caret_pos, caret_pos); 395 SetTextAndSelectedRange(text, range); 396 397 if (update_popup) 398 UpdatePopup(); 399 400 if (notify_text_changed) 401 TextChanged(); 402} 403 404void OmniboxViewViews::SetForcedQuery() { 405 const string16 current_text(text()); 406 const size_t start = current_text.find_first_not_of(kWhitespaceUTF16); 407 if (start == string16::npos || (current_text[start] != '?')) 408 SetUserText(ASCIIToUTF16("?")); 409 else 410 SelectRange(ui::Range(current_text.size(), start + 1)); 411} 412 413bool OmniboxViewViews::IsSelectAll() const { 414 // TODO(oshima): IME support. 415 return text() == GetSelectedText(); 416} 417 418bool OmniboxViewViews::DeleteAtEndPressed() { 419 return delete_at_end_pressed_; 420} 421 422void OmniboxViewViews::GetSelectionBounds(string16::size_type* start, 423 string16::size_type* end) const { 424 const ui::Range range = GetSelectedRange(); 425 *start = static_cast<size_t>(range.start()); 426 *end = static_cast<size_t>(range.end()); 427} 428 429void OmniboxViewViews::SelectAll(bool reversed) { 430 views::Textfield::SelectAll(reversed); 431} 432 433void OmniboxViewViews::UpdatePopup() { 434 model()->SetInputInProgress(true); 435 if (ime_candidate_window_open_) 436 return; 437 if (!model()->has_focus()) 438 return; 439 440 // Prevent inline autocomplete when the caret isn't at the end of the text, 441 // and during IME composition editing. 442 const ui::Range sel = GetSelectedRange(); 443 model()->StartAutocomplete( 444 !sel.is_empty(), 445 sel.GetMax() < text().length() || IsIMEComposing()); 446} 447 448void OmniboxViewViews::SetFocus() { 449 RequestFocus(); 450 // Restore caret visibility if focus is explicitly requested. This is 451 // necessary because if we already have invisible focus, the RequestFocus() 452 // call above will short-circuit, preventing us from reaching 453 // OmniboxEditModel::OnSetFocus(), which handles restoring visibility when the 454 // omnibox regains focus after losing focus. 455 model()->SetCaretVisibility(true); 456} 457 458void OmniboxViewViews::ApplyCaretVisibility() { 459 SetCursorEnabled(model()->is_caret_visible()); 460} 461 462void OmniboxViewViews::OnTemporaryTextMaybeChanged( 463 const string16& display_text, 464 bool save_original_selection, 465 bool notify_text_changed) { 466 if (save_original_selection) 467 saved_temporary_selection_ = GetSelectedRange(); 468 469 SetWindowTextAndCaretPos(display_text, display_text.length(), false, 470 notify_text_changed); 471} 472 473bool OmniboxViewViews::OnInlineAutocompleteTextMaybeChanged( 474 const string16& display_text, 475 size_t user_text_length) { 476 if (display_text == text()) 477 return false; 478 ui::Range range(display_text.size(), user_text_length); 479 SetTextAndSelectedRange(display_text, range); 480 TextChanged(); 481 return true; 482} 483 484void OmniboxViewViews::OnRevertTemporaryText() { 485 SelectRange(saved_temporary_selection_); 486 // We got here because the user hit the Escape key. We explicitly don't call 487 // TextChanged(), since calling it breaks Instant-Extended, and isn't needed 488 // otherwise (in regular non-Instant or Instant-but-not-Extended modes). 489 // 490 // Why it breaks Instant-Extended: Instant handles the Escape key separately 491 // (cf: OmniboxEditModel::RevertTemporaryText). Calling TextChanged() makes 492 // the page think the user additionally typed some text, causing it to update 493 // its suggestions dropdown with new suggestions, which is wrong. 494 // 495 // Why it isn't needed: OmniboxPopupModel::ResetToDefaultMatch() has already 496 // been called by now; it would've called TextChanged() if it was warranted. 497} 498 499void OmniboxViewViews::OnBeforePossibleChange() { 500 // Record our state. 501 text_before_change_ = text(); 502 sel_before_change_ = GetSelectedRange(); 503 ime_composing_before_change_ = IsIMEComposing(); 504} 505 506bool OmniboxViewViews::OnAfterPossibleChange() { 507 // See if the text or selection have changed since OnBeforePossibleChange(). 508 const string16 new_text = text(); 509 const ui::Range new_sel = GetSelectedRange(); 510 const bool text_changed = (new_text != text_before_change_) || 511 (ime_composing_before_change_ != IsIMEComposing()); 512 const bool selection_differs = 513 !((sel_before_change_.is_empty() && new_sel.is_empty()) || 514 sel_before_change_.EqualsIgnoringDirection(new_sel)); 515 516 // When the user has deleted text, we don't allow inline autocomplete. Make 517 // sure to not flag cases like selecting part of the text and then pasting 518 // (or typing) the prefix of that selection. (We detect these by making 519 // sure the caret, which should be after any insertion, hasn't moved 520 // forward of the old selection start.) 521 const bool just_deleted_text = 522 (text_before_change_.length() > new_text.length()) && 523 (new_sel.start() <= sel_before_change_.GetMin()); 524 525 const bool something_changed = model()->OnAfterPossibleChange( 526 text_before_change_, new_text, new_sel.start(), new_sel.end(), 527 selection_differs, text_changed, just_deleted_text, !IsIMEComposing()); 528 529 // If only selection was changed, we don't need to call model()'s 530 // OnChanged() method, which is called in TextChanged(). 531 // But we still need to call EmphasizeURLComponents() to make sure the text 532 // attributes are updated correctly. 533 if (something_changed && text_changed) 534 TextChanged(); 535 else if (selection_differs) 536 EmphasizeURLComponents(); 537 else if (delete_at_end_pressed_) 538 model()->OnChanged(); 539 540 return something_changed; 541} 542 543gfx::NativeView OmniboxViewViews::GetNativeView() const { 544 return GetWidget()->GetNativeView(); 545} 546 547gfx::NativeView OmniboxViewViews::GetRelativeWindowForPopup() const { 548 return GetWidget()->GetTopLevelWidget()->GetNativeView(); 549} 550 551void OmniboxViewViews::SetInstantSuggestion(const string16& input) { 552#if defined(OS_WIN) || defined(USE_AURA) 553 location_bar_view_->SetInstantSuggestion(input); 554#endif 555} 556 557string16 OmniboxViewViews::GetInstantSuggestion() const { 558#if defined(OS_WIN) || defined(USE_AURA) 559 return location_bar_view_->GetInstantSuggestion(); 560#else 561 return string16(); 562#endif 563} 564 565int OmniboxViewViews::TextWidth() const { 566 return native_wrapper_->GetWidthNeededForText(); 567} 568 569bool OmniboxViewViews::IsImeComposing() const { 570 return false; 571} 572 573int OmniboxViewViews::GetMaxEditWidth(int entry_width) const { 574 return entry_width; 575} 576 577views::View* OmniboxViewViews::AddToView(views::View* parent) { 578 parent->AddChildView(this); 579 return this; 580} 581 582int OmniboxViewViews::OnPerformDrop(const ui::DropTargetEvent& event) { 583 NOTIMPLEMENTED(); 584 return ui::DragDropTypes::DRAG_NONE; 585} 586 587//////////////////////////////////////////////////////////////////////////////// 588// OmniboxViewViews, views::TextfieldController implementation: 589 590void OmniboxViewViews::ContentsChanged(views::Textfield* sender, 591 const string16& new_contents) { 592} 593 594bool OmniboxViewViews::HandleKeyEvent(views::Textfield* textfield, 595 const ui::KeyEvent& event) { 596 delete_at_end_pressed_ = false; 597 598 if (event.key_code() == ui::VKEY_BACK) { 599 // No extra handling is needed in keyword search mode, if there is a 600 // non-empty selection, or if the cursor is not leading the text. 601 if (model()->is_keyword_hint() || model()->keyword().empty() || 602 HasSelection() || GetCursorPosition() != 0) 603 return false; 604 model()->ClearKeyword(text()); 605 return true; 606 } 607 608 if (event.key_code() == ui::VKEY_DELETE && !event.IsAltDown()) { 609 delete_at_end_pressed_ = 610 (!HasSelection() && GetCursorPosition() == text().length()); 611 } 612 613 // Handle the right-arrow key for LTR text and the left-arrow key for RTL text 614 // if there is an Instant suggestion (gray text) that needs to be committed. 615 if (GetCursorPosition() == text().length()) { 616 base::i18n::TextDirection direction = GetTextDirection(); 617 if ((direction == base::i18n::LEFT_TO_RIGHT && 618 event.key_code() == ui::VKEY_RIGHT) || 619 (direction == base::i18n::RIGHT_TO_LEFT && 620 event.key_code() == ui::VKEY_LEFT)) { 621 return model()->CommitSuggestedText(true); 622 } 623 } 624 625 return false; 626} 627 628void OmniboxViewViews::OnBeforeUserAction(views::Textfield* sender) { 629 OnBeforePossibleChange(); 630} 631 632void OmniboxViewViews::OnAfterUserAction(views::Textfield* sender) { 633 OnAfterPossibleChange(); 634} 635 636void OmniboxViewViews::OnAfterCutOrCopy() { 637 ui::Clipboard* cb = ui::Clipboard::GetForCurrentThread(); 638 string16 selected_text; 639 cb->ReadText(ui::Clipboard::BUFFER_STANDARD, &selected_text); 640 GURL url; 641 bool write_url; 642 model()->AdjustTextForCopy(GetSelectedRange().GetMin(), IsSelectAll(), 643 &selected_text, &url, &write_url); 644 if (write_url) { 645 DoCopyURL(url, selected_text, model()->profile()); 646 } else { 647 ui::ScopedClipboardWriter scoped_clipboard_writer( 648 ui::Clipboard::GetForCurrentThread(), 649 ui::Clipboard::BUFFER_STANDARD, 650 content::BrowserContext::GetMarkerForOffTheRecordContext( 651 model()->profile())); 652 scoped_clipboard_writer.WriteText(selected_text); 653 } 654} 655 656void OmniboxViewViews::OnGetDragOperationsForTextfield(int* drag_operations) { 657 string16 selected_text = GetSelectedText(); 658 GURL url; 659 bool write_url; 660 model()->AdjustTextForCopy(GetSelectedRange().GetMin(), IsSelectAll(), 661 &selected_text, &url, &write_url); 662 if (write_url) 663 *drag_operations |= ui::DragDropTypes::DRAG_LINK; 664} 665 666void OmniboxViewViews::OnWriteDragData(ui::OSExchangeData* data) { 667 string16 selected_text = GetSelectedText(); 668 GURL url; 669 bool write_url; 670 bool is_all_selected = IsSelectAll(); 671 model()->AdjustTextForCopy(GetSelectedRange().GetMin(), is_all_selected, 672 &selected_text, &url, &write_url); 673 data->SetString(selected_text); 674 if (write_url) { 675 gfx::Image favicon; 676 string16 title = selected_text; 677 if (is_all_selected) 678 model()->GetDataForURLExport(&url, &title, &favicon); 679 button_drag_utils::SetURLAndDragImage(url, title, favicon.AsImageSkia(), 680 data, GetWidget()); 681 data->SetURL(url, title); 682 } 683} 684 685void OmniboxViewViews::AppendDropFormats( 686 int* formats, 687 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) { 688 *formats = *formats | ui::OSExchangeData::URL; 689} 690 691int OmniboxViewViews::OnDrop(const ui::OSExchangeData& data) { 692 if (HasTextBeingDragged()) 693 return ui::DragDropTypes::DRAG_NONE; 694 695 if (data.HasURL()) { 696 GURL url; 697 string16 title; 698 if (data.GetURLAndTitle(&url, &title)) { 699 string16 text(StripJavascriptSchemas(UTF8ToUTF16(url.spec()))); 700 if (model()->CanPasteAndGo(text)) { 701 model()->PasteAndGo(text); 702 return ui::DragDropTypes::DRAG_COPY; 703 } 704 } 705 } else if (data.HasString()) { 706 string16 text; 707 if (data.GetString(&text)) { 708 string16 collapsed_text(CollapseWhitespace(text, true)); 709 if (model()->CanPasteAndGo(collapsed_text)) 710 model()->PasteAndGo(collapsed_text); 711 return ui::DragDropTypes::DRAG_COPY; 712 } 713 } 714 715 return ui::DragDropTypes::DRAG_NONE; 716} 717 718void OmniboxViewViews::UpdateContextMenu(ui::SimpleMenuModel* menu_contents) { 719 // Minor note: We use IDC_ for command id here while the underlying textfield 720 // is using IDS_ for all its command ids. This is because views cannot depend 721 // on IDC_ for now. 722 menu_contents->AddItemWithStringId(IDC_EDIT_SEARCH_ENGINES, 723 IDS_EDIT_SEARCH_ENGINES); 724 725 if (chrome::IsQueryExtractionEnabled(model()->profile())) { 726 int copy_position = menu_contents->GetIndexOfCommandId(IDS_APP_COPY); 727 DCHECK(copy_position >= 0); 728 menu_contents->InsertItemWithStringIdAt( 729 copy_position + 1, IDC_COPY_URL, IDS_COPY_URL); 730 } 731 732 int paste_position = menu_contents->GetIndexOfCommandId(IDS_APP_PASTE); 733 DCHECK(paste_position >= 0); 734 menu_contents->InsertItemWithStringIdAt( 735 paste_position + 1, IDS_PASTE_AND_GO, IDS_PASTE_AND_GO); 736} 737 738bool OmniboxViewViews::IsCommandIdEnabled(int command_id) const { 739 if (command_id == IDS_PASTE_AND_GO) 740 return model()->CanPasteAndGo(GetClipboardText()); 741 if (command_id == IDC_COPY_URL) { 742 return !model()->user_input_in_progress() && 743 (toolbar_model()->GetSearchTermsType() != 744 ToolbarModel::NO_SEARCH_TERMS); 745 } 746 return command_updater()->IsCommandEnabled(command_id); 747} 748 749bool OmniboxViewViews::IsItemForCommandIdDynamic(int command_id) const { 750 return command_id == IDS_PASTE_AND_GO; 751} 752 753string16 OmniboxViewViews::GetLabelForCommandId(int command_id) const { 754 if (command_id == IDS_PASTE_AND_GO) { 755 return l10n_util::GetStringUTF16( 756 model()->IsPasteAndSearch(GetClipboardText()) ? 757 IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO); 758 } 759 760 return string16(); 761} 762 763bool OmniboxViewViews::HandlesCommand(int command_id) const { 764 // See description in OnPaste() for details on why we need to handle paste. 765 return command_id == IDS_APP_PASTE; 766} 767 768void OmniboxViewViews::ExecuteCommand(int command_id, int event_flags) { 769 switch (command_id) { 770 // These commands don't invoke the popup via OnBefore/AfterPossibleChange(). 771 case IDS_PASTE_AND_GO: 772 model()->PasteAndGo(GetClipboardText()); 773 break; 774 case IDC_EDIT_SEARCH_ENGINES: 775 command_updater()->ExecuteCommand(command_id); 776 break; 777 case IDC_COPY_URL: 778 CopyURL(); 779 break; 780 781 case IDS_APP_PASTE: 782 OnBeforePossibleChange(); 783 OnPaste(); 784 OnAfterPossibleChange(); 785 break; 786 default: 787 OnBeforePossibleChange(); 788 command_updater()->ExecuteCommand(command_id); 789 OnAfterPossibleChange(); 790 break; 791 } 792} 793 794#if defined(OS_CHROMEOS) 795void OmniboxViewViews::CandidateWindowOpened( 796 chromeos::input_method::InputMethodManager* manager) { 797 ime_candidate_window_open_ = true; 798 CloseOmniboxPopup(); 799} 800 801void OmniboxViewViews::CandidateWindowClosed( 802 chromeos::input_method::InputMethodManager* manager) { 803 ime_candidate_window_open_ = false; 804 UpdatePopup(); 805} 806#endif 807 808//////////////////////////////////////////////////////////////////////////////// 809// OmniboxViewViews, private: 810 811int OmniboxViewViews::GetOmniboxTextLength() const { 812 // TODO(oshima): Support instant, IME. 813 return static_cast<int>(text().length()); 814} 815 816void OmniboxViewViews::EmphasizeURLComponents() { 817 // See whether the contents are a URL with a non-empty host portion, which we 818 // should emphasize. To check for a URL, rather than using the type returned 819 // by Parse(), ask the model, which will check the desired page transition for 820 // this input. This can tell us whether an UNKNOWN input string is going to 821 // be treated as a search or a navigation, and is the same method the Paste 822 // And Go system uses. 823 url_parse::Component scheme, host; 824 AutocompleteInput::ParseForEmphasizeComponents(text(), &scheme, &host); 825 bool grey_out_url = text().substr(scheme.begin, scheme.len) == 826 UTF8ToUTF16(extensions::kExtensionScheme); 827 bool grey_base = model()->CurrentTextIsURL() && 828 (host.is_nonempty() || grey_out_url); 829 SetColor(location_bar_view_->GetColor( 830 security_level_, 831 grey_base ? LocationBarView::DEEMPHASIZED_TEXT : LocationBarView::TEXT)); 832 if (grey_base && !grey_out_url) { 833 ApplyColor( 834 location_bar_view_->GetColor(security_level_, LocationBarView::TEXT), 835 ui::Range(host.begin, host.end())); 836 } 837 838 // Emphasize the scheme for security UI display purposes (if necessary). 839 // Note that we check CurrentTextIsURL() because if we're replacing search 840 // URLs with search terms, we may have a non-URL even when the user is not 841 // editing; and in some cases, e.g. for "site:foo.com" searches, the parser 842 // may have incorrectly identified a qualifier as a scheme. 843 SetStyle(gfx::DIAGONAL_STRIKE, false); 844 if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() && 845 scheme.is_nonempty() && (security_level_ != ToolbarModel::NONE)) { 846 SkColor security_color = location_bar_view_->GetColor( 847 security_level_, LocationBarView::SECURITY_TEXT); 848 const bool strike = (security_level_ == ToolbarModel::SECURITY_ERROR); 849 const ui::Range scheme_range(scheme.begin, scheme.end()); 850 ApplyColor(security_color, scheme_range); 851 ApplyStyle(gfx::DIAGONAL_STRIKE, strike, scheme_range); 852 } 853} 854 855void OmniboxViewViews::SetTextAndSelectedRange(const string16& text, 856 const ui::Range& range) { 857 SetText(text); 858 SelectRange(range); 859} 860 861string16 OmniboxViewViews::GetSelectedText() const { 862 // TODO(oshima): Support instant, IME. 863 return views::Textfield::GetSelectedText(); 864} 865 866void OmniboxViewViews::CopyURL() { 867 DoCopyURL(toolbar_model()->GetURL(), 868 toolbar_model()->GetText(false), 869 model()->profile()); 870} 871 872void OmniboxViewViews::OnPaste() { 873 // Replace the selection if we have something to paste. 874 const string16 text(GetClipboardText()); 875 if (!text.empty()) { 876 // Record this paste, so we can do different behavior. 877 model()->on_paste(); 878 // Force a Paste operation to trigger the text_changed code in 879 // OnAfterPossibleChange(), even if identical contents are pasted. 880 text_before_change_.clear(); 881 ReplaceSelection(text); 882 } 883} 884