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