omnibox_view_views.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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/command_line.h"
8#include "base/logging.h"
9#include "base/metrics/histogram.h"
10#include "base/strings/string_util.h"
11#include "base/strings/utf_string_conversions.h"
12#include "chrome/app/chrome_command_ids.h"
13#include "chrome/browser/autocomplete/autocomplete_input.h"
14#include "chrome/browser/autocomplete/autocomplete_match.h"
15#include "chrome/browser/bookmarks/bookmark_node_data.h"
16#include "chrome/browser/command_updater.h"
17#include "chrome/browser/search/search.h"
18#include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
19#include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
20#include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
21#include "chrome/browser/ui/view_ids.h"
22#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
23#include "chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h"
24#include "chrome/common/chrome_switches.h"
25#include "content/public/browser/web_contents.h"
26#include "extensions/common/constants.h"
27#include "grit/app_locale_settings.h"
28#include "grit/generated_resources.h"
29#include "grit/ui_strings.h"
30#include "net/base/escape.h"
31#include "third_party/skia/include/core/SkColor.h"
32#include "ui/base/accessibility/accessible_view_state.h"
33#include "ui/base/clipboard/scoped_clipboard_writer.h"
34#include "ui/base/dragdrop/drag_drop_types.h"
35#include "ui/base/dragdrop/os_exchange_data.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/events/event.h"
42#include "ui/gfx/canvas.h"
43#include "ui/gfx/font_list.h"
44#include "ui/gfx/selection_model.h"
45#include "ui/views/border.h"
46#include "ui/views/button_drag_utils.h"
47#include "ui/views/controls/textfield/textfield.h"
48#include "ui/views/ime/input_method.h"
49#include "ui/views/layout/fill_layout.h"
50#include "ui/views/views_delegate.h"
51#include "ui/views/widget/widget.h"
52#include "url/gurl.h"
53
54#if defined(OS_WIN)
55#include "chrome/browser/browser_process.h"
56#endif
57
58#if defined(USE_AURA)
59#include "ui/aura/client/focus_client.h"
60#include "ui/aura/root_window.h"
61#include "ui/compositor/layer.h"
62#endif
63
64namespace {
65
66// Stores omnibox state for each tab.
67struct OmniboxState : public base::SupportsUserData::Data {
68  static const char kKey[];
69
70  OmniboxState(const OmniboxEditModel::State& model_state,
71               const gfx::Range& selection,
72               const gfx::Range& saved_selection_for_focus_change);
73  virtual ~OmniboxState();
74
75  const OmniboxEditModel::State model_state;
76
77  // We store both the actual selection and any saved selection (for when the
78  // omnibox is not focused).  This allows us to properly handle cases like
79  // selecting text, tabbing out of the omnibox, switching tabs away and back,
80  // and tabbing back into the omnibox.
81  const gfx::Range selection;
82  const gfx::Range saved_selection_for_focus_change;
83};
84
85// static
86const char OmniboxState::kKey[] = "OmniboxState";
87
88OmniboxState::OmniboxState(const OmniboxEditModel::State& model_state,
89                           const gfx::Range& selection,
90                           const gfx::Range& saved_selection_for_focus_change)
91    : model_state(model_state),
92      selection(selection),
93      saved_selection_for_focus_change(saved_selection_for_focus_change) {
94}
95
96OmniboxState::~OmniboxState() {}
97
98// We'd like to set the text input type to TEXT_INPUT_TYPE_URL, because this
99// triggers URL-specific layout in software keyboards, e.g. adding top-level "/"
100// and ".com" keys for English.  However, this also causes IMEs to default to
101// Latin character mode, which makes entering search queries difficult for IME
102// users.  Therefore, we try to guess whether an IME will be used based on the
103// application language, and set the input type accordingly.
104ui::TextInputType DetermineTextInputType() {
105#if defined(OS_WIN)
106  DCHECK(g_browser_process);
107  const std::string& locale = g_browser_process->GetApplicationLocale();
108  const std::string& language = locale.substr(0, 2);
109  // Assume CJK + Thai users are using an IME.
110  if (language == "ja" ||
111      language == "ko" ||
112      language == "th" ||
113      language == "zh")
114    return ui::TEXT_INPUT_TYPE_SEARCH;
115#endif
116  return ui::TEXT_INPUT_TYPE_URL;
117}
118
119bool IsOmniboxAutoCompletionForImeEnabled() {
120  return !CommandLine::ForCurrentProcess()->HasSwitch(
121      switches::kDisableOmniboxAutoCompletionForIme);
122}
123
124}  // namespace
125
126// static
127const char OmniboxViewViews::kViewClassName[] = "OmniboxViewViews";
128
129OmniboxViewViews::OmniboxViewViews(OmniboxEditController* controller,
130                                   Profile* profile,
131                                   CommandUpdater* command_updater,
132                                   bool popup_window_mode,
133                                   LocationBarView* location_bar,
134                                   const gfx::FontList& font_list)
135    : OmniboxView(profile, controller, command_updater),
136      popup_window_mode_(popup_window_mode),
137      security_level_(ToolbarModel::NONE),
138      saved_selection_for_focus_change_(gfx::Range::InvalidRange()),
139      ime_composing_before_change_(false),
140      delete_at_end_pressed_(false),
141      location_bar_view_(location_bar),
142      ime_candidate_window_open_(false),
143      select_all_on_mouse_release_(false),
144      select_all_on_gesture_tap_(false) {
145  SetBorder(views::Border::NullBorder());
146  set_id(VIEW_ID_OMNIBOX);
147  SetFontList(font_list);
148}
149
150OmniboxViewViews::~OmniboxViewViews() {
151#if defined(OS_CHROMEOS)
152  chromeos::input_method::InputMethodManager::Get()->
153      RemoveCandidateWindowObserver(this);
154#endif
155
156  // Explicitly teardown members which have a reference to us.  Just to be safe
157  // we want them to be destroyed before destroying any other internal state.
158  popup_view_.reset();
159}
160
161////////////////////////////////////////////////////////////////////////////////
162// OmniboxViewViews public:
163
164void OmniboxViewViews::Init() {
165  set_controller(this);
166  SetTextInputType(DetermineTextInputType());
167  SetBackgroundColor(location_bar_view_->GetColor(
168      ToolbarModel::NONE, LocationBarView::BACKGROUND));
169
170  if (popup_window_mode_)
171    SetReadOnly(true);
172
173  // Initialize the popup view using the same font.
174  popup_view_.reset(OmniboxPopupContentsView::Create(
175      GetFontList(), this, model(), location_bar_view_));
176
177#if defined(OS_CHROMEOS)
178  chromeos::input_method::InputMethodManager::Get()->
179      AddCandidateWindowObserver(this);
180#endif
181}
182
183////////////////////////////////////////////////////////////////////////////////
184// OmniboxViewViews, views::Textfield implementation:
185
186void OmniboxViewViews::AboutToRequestFocusFromTabTraversal(bool reverse) {
187  if (chrome::GetOriginChipV2HideTrigger() ==
188      chrome::ORIGIN_CHIP_V2_HIDE_ON_MOUSE_RELEASE) {
189    controller()->GetToolbarModel()->set_origin_chip_enabled(false);
190    controller()->OnChanged();
191  }
192}
193
194const char* OmniboxViewViews::GetClassName() const {
195  return kViewClassName;
196}
197
198void OmniboxViewViews::OnGestureEvent(ui::GestureEvent* event) {
199  if (!HasFocus() && event->type() == ui::ET_GESTURE_TAP_DOWN) {
200    select_all_on_gesture_tap_ = true;
201
202    // If we're trying to select all on tap, invalidate any saved selection lest
203    // restoring it fights with the "select all" action.
204    saved_selection_for_focus_change_ = gfx::Range::InvalidRange();
205  }
206
207  if (select_all_on_gesture_tap_ && event->type() == ui::ET_GESTURE_TAP)
208    SelectAll(false);
209
210  if (event->type() == ui::ET_GESTURE_TAP ||
211      event->type() == ui::ET_GESTURE_TAP_CANCEL ||
212      event->type() == ui::ET_GESTURE_TWO_FINGER_TAP ||
213      event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
214      event->type() == ui::ET_GESTURE_PINCH_BEGIN ||
215      event->type() == ui::ET_GESTURE_LONG_PRESS ||
216      event->type() == ui::ET_GESTURE_LONG_TAP) {
217    select_all_on_gesture_tap_ = false;
218  }
219
220  views::Textfield::OnGestureEvent(event);
221}
222
223void OmniboxViewViews::GetAccessibleState(ui::AccessibleViewState* state) {
224  location_bar_view_->GetAccessibleState(state);
225  state->role = ui::AccessibilityTypes::ROLE_TEXT;
226}
227
228bool OmniboxViewViews::OnMousePressed(const ui::MouseEvent& event) {
229  select_all_on_mouse_release_ =
230      (event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton()) &&
231      (!HasFocus() || (model()->focus_state() == OMNIBOX_FOCUS_INVISIBLE));
232  if (select_all_on_mouse_release_) {
233    // Restore caret visibility whenever the user clicks in the omnibox in a way
234    // that would give it focus.  We must handle this case separately here
235    // because if the omnibox currently has invisible focus, the mouse event
236    // won't trigger either SetFocus() or OmniboxEditModel::OnSetFocus().
237    model()->SetCaretVisibility(true);
238
239    // When we're going to select all on mouse release, invalidate any saved
240    // selection lest restoring it fights with the "select all" action.  It's
241    // possible to later set select_all_on_mouse_release_ back to false, but
242    // that happens for things like dragging, which are cases where having
243    // invalidated this saved selection is still OK.
244    saved_selection_for_focus_change_ = gfx::Range::InvalidRange();
245  }
246  return views::Textfield::OnMousePressed(event);
247}
248
249bool OmniboxViewViews::OnMouseDragged(const ui::MouseEvent& event) {
250  if (ExceededDragThreshold(event.location() - last_click_location()))
251    select_all_on_mouse_release_ = false;
252  return views::Textfield::OnMouseDragged(event);
253}
254
255void OmniboxViewViews::OnMouseReleased(const ui::MouseEvent& event) {
256  views::Textfield::OnMouseReleased(event);
257  if (event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton()) {
258    // When the user has clicked and released to give us focus, select all
259    // unless we're omitting the URL (in which case refining an existing query
260    // is common enough that we do click-to-place-cursor).
261    if (select_all_on_mouse_release_ &&
262        !controller()->GetToolbarModel()->WouldReplaceURL()) {
263      // Select all in the reverse direction so as not to scroll the caret
264      // into view and shift the contents jarringly.
265      SelectAll(true);
266    }
267
268    // If we should hide the origin chip in response to mouse release, check
269    // first that only the left or only the right button was pressed prior to
270    // the release.  If multiple buttons were pressed, we don't want to hide the
271    // chip yet.  If only the middle mouse button was pressed, the Omnibox won't
272    // receive focus so we don't want to hide the chip in this case.
273    if (chrome::GetOriginChipV2HideTrigger() ==
274        chrome::ORIGIN_CHIP_V2_HIDE_ON_MOUSE_RELEASE) {
275      controller()->GetToolbarModel()->set_origin_chip_enabled(false);
276      controller()->OnChanged();
277    }
278  }
279  select_all_on_mouse_release_ = false;
280}
281
282bool OmniboxViewViews::OnKeyPressed(const ui::KeyEvent& event) {
283  // Skip processing of [Alt]+<num-pad digit> Unicode alt key codes.
284  // Otherwise, if num-lock is off, the events are handled as [Up], [Down], etc.
285  if (event.IsUnicodeKeyCode())
286    return views::Textfield::OnKeyPressed(event);
287
288  const bool shift = event.IsShiftDown();
289  const bool control = event.IsControlDown();
290  const bool alt = event.IsAltDown() || event.IsAltGrDown();
291  switch (event.key_code()) {
292    case ui::VKEY_RETURN:
293      model()->AcceptInput(alt ? NEW_FOREGROUND_TAB : CURRENT_TAB, false);
294      return true;
295    case ui::VKEY_ESCAPE:
296      return model()->OnEscapeKeyPressed();
297    case ui::VKEY_CONTROL:
298      model()->OnControlKeyChanged(true);
299      break;
300    case ui::VKEY_DELETE:
301      if (shift && model()->popup_model()->IsOpen())
302        model()->popup_model()->TryDeletingCurrentItem();
303      break;
304    case ui::VKEY_UP:
305      if (!read_only()) {
306        model()->OnUpOrDownKeyPressed(-1);
307        return true;
308      }
309      break;
310    case ui::VKEY_DOWN:
311      if (!read_only()) {
312        model()->OnUpOrDownKeyPressed(1);
313        return true;
314      }
315      break;
316    case ui::VKEY_PRIOR:
317      if (control || alt || shift)
318        return false;
319      model()->OnUpOrDownKeyPressed(-1 * model()->result().size());
320      return true;
321    case ui::VKEY_NEXT:
322      if (control || alt || shift)
323        return false;
324      model()->OnUpOrDownKeyPressed(model()->result().size());
325      return true;
326    case ui::VKEY_V:
327      if (control && !alt && !read_only()) {
328        ExecuteCommand(IDS_APP_PASTE, 0);
329        return true;
330      }
331      break;
332    case ui::VKEY_INSERT:
333      if (shift && !control && !read_only()) {
334        ExecuteCommand(IDS_APP_PASTE, 0);
335        return true;
336      }
337      break;
338    default:
339      break;
340  }
341
342  return views::Textfield::OnKeyPressed(event) || HandleEarlyTabActions(event);
343}
344
345bool OmniboxViewViews::OnKeyReleased(const ui::KeyEvent& event) {
346  // The omnibox contents may change while the control key is pressed.
347  if (event.key_code() == ui::VKEY_CONTROL)
348    model()->OnControlKeyChanged(false);
349  return views::Textfield::OnKeyReleased(event);
350}
351
352bool OmniboxViewViews::SkipDefaultKeyEventProcessing(
353    const ui::KeyEvent& event) {
354  if (views::FocusManager::IsTabTraversalKeyEvent(event) &&
355      ((model()->is_keyword_hint() && !event.IsShiftDown()) ||
356       model()->popup_model()->IsOpen())) {
357    return true;
358  }
359  return Textfield::SkipDefaultKeyEventProcessing(event);
360}
361
362bool OmniboxViewViews::HandleEarlyTabActions(const ui::KeyEvent& event) {
363  // This must run before acclerator handling invokes a focus change on tab.
364  // Note the parallel with SkipDefaultKeyEventProcessing above.
365  if (views::FocusManager::IsTabTraversalKeyEvent(event)) {
366    if (model()->is_keyword_hint() && !event.IsShiftDown()) {
367      model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB);
368      return true;
369    }
370    if (model()->popup_model()->IsOpen()) {
371      if (event.IsShiftDown() &&
372          model()->popup_model()->selected_line_state() ==
373              OmniboxPopupModel::KEYWORD) {
374        model()->ClearKeyword(text());
375      } else {
376        model()->OnUpOrDownKeyPressed(event.IsShiftDown() ? -1 : 1);
377      }
378      return true;
379    }
380  }
381
382  return false;
383}
384
385void OmniboxViewViews::OnFocus() {
386  views::Textfield::OnFocus();
387  // TODO(oshima): Get control key state.
388  model()->OnSetFocus(false);
389  // Don't call controller()->OnSetFocus, this view has already acquired focus.
390
391  // Restore the selection we saved in OnBlur() if it's still valid.
392  if (saved_selection_for_focus_change_.IsValid()) {
393    SelectRange(saved_selection_for_focus_change_);
394    saved_selection_for_focus_change_ = gfx::Range::InvalidRange();
395  }
396}
397
398void OmniboxViewViews::OnBlur() {
399  // Save the user's existing selection to restore it later.
400  saved_selection_for_focus_change_ = GetSelectedRange();
401
402  views::Textfield::OnBlur();
403  gfx::NativeView native_view = NULL;
404#if defined(USE_AURA)
405  views::Widget* widget = GetWidget();
406  if (widget) {
407    aura::client::FocusClient* client =
408        aura::client::GetFocusClient(widget->GetNativeView());
409    if (client)
410      native_view = client->GetFocusedWindow();
411  }
412#endif
413  model()->OnWillKillFocus(native_view);
414  // Close the popup.
415  CloseOmniboxPopup();
416
417  // Tell the model to reset itself.
418  model()->OnKillFocus();
419
420  // If user input is not in progress, re-enable the origin chip and URL
421  // replacement.  This addresses the case where the URL was shown by a call
422  // to ShowURL().  If the Omnibox acheived focus by other means, the calls to
423  // set_url_replacement_enabled, UpdatePermanentText and RevertAll are not
424  // required (a call to OnChanged would be sufficient) but do no harm.
425  if (chrome::ShouldDisplayOriginChipV2() &&
426      !model()->user_input_in_progress()) {
427    controller()->GetToolbarModel()->set_origin_chip_enabled(true);
428    controller()->GetToolbarModel()->set_url_replacement_enabled(true);
429    model()->UpdatePermanentText();
430    RevertAll();
431  }
432
433  // Make sure the beginning of the text is visible.
434  SelectRange(gfx::Range(0));
435}
436
437base::string16 OmniboxViewViews::GetSelectionClipboardText() const {
438  return SanitizeTextForPaste(Textfield::GetSelectionClipboardText());
439}
440
441////////////////////////////////////////////////////////////////////////////////
442// OmniboxViewViews, OmniboxView implementation:
443
444void OmniboxViewViews::SaveStateToTab(content::WebContents* tab) {
445  DCHECK(tab);
446
447  // We don't want to keep the IME status, so force quit the current
448  // session here.  It may affect the selection status, so order is
449  // also important.
450  if (IsIMEComposing()) {
451    GetTextInputClient()->ConfirmCompositionText();
452    GetInputMethod()->CancelComposition(this);
453  }
454
455  // NOTE: GetStateForTabSwitch() may affect GetSelectedRange(), so order is
456  // important.
457  OmniboxEditModel::State state = model()->GetStateForTabSwitch();
458  tab->SetUserData(OmniboxState::kKey, new OmniboxState(
459      state, GetSelectedRange(), saved_selection_for_focus_change_));
460}
461
462void OmniboxViewViews::OnTabChanged(const content::WebContents* web_contents) {
463  security_level_ = controller()->GetToolbarModel()->GetSecurityLevel(false);
464
465  const OmniboxState* state = static_cast<OmniboxState*>(
466      web_contents->GetUserData(&OmniboxState::kKey));
467  model()->RestoreState(state ? &state->model_state : NULL);
468  if (state) {
469    // This assumes that the omnibox has already been focused or blurred as
470    // appropriate; otherwise, a subsequent OnFocus() or OnBlur() call could
471    // goof up the selection.  See comments at the end of
472    // BrowserView::ActiveTabChanged().
473    SelectRange(state->selection);
474    saved_selection_for_focus_change_ = state->saved_selection_for_focus_change;
475  }
476
477  // TODO(msw|oshima): Consider saving/restoring edit history.
478  ClearEditHistory();
479}
480
481void OmniboxViewViews::Update() {
482  if (chrome::ShouldDisplayOriginChip() || chrome::ShouldDisplayOriginChipV2())
483    set_placeholder_text(GetHintText());
484
485  const ToolbarModel::SecurityLevel old_security_level = security_level_;
486  security_level_ = controller()->GetToolbarModel()->GetSecurityLevel(false);
487  if (model()->UpdatePermanentText()) {
488    // Something visibly changed.  Re-enable URL replacement.
489    controller()->GetToolbarModel()->set_url_replacement_enabled(true);
490    model()->UpdatePermanentText();
491
492    // Tweak: if the user had all the text selected, select all the new text.
493    // This makes one particular case better: the user clicks in the box to
494    // change it right before the permanent URL is changed.  Since the new URL
495    // is still fully selected, the user's typing will replace the edit contents
496    // as they'd intended.
497    const bool was_select_all = !text().empty() && IsSelectAll();
498    const bool was_reversed = GetSelectedRange().is_reversed();
499
500    RevertAll();
501
502    // Only select all when we have focus.  If we don't have focus, selecting
503    // all is unnecessary since the selection will change on regaining focus,
504    // and can in fact cause artifacts, e.g. if the user is on the NTP and
505    // clicks a link to navigate, causing |was_select_all| to be vacuously true
506    // for the empty omnibox, and we then select all here, leading to the
507    // trailing portion of a long URL being scrolled into view.  We could try
508    // and address cases like this, but it seems better to just not muck with
509    // things when the omnibox isn't focused to begin with.
510    if (was_select_all && model()->has_focus())
511      SelectAll(was_reversed);
512  } else if (old_security_level != security_level_) {
513    EmphasizeURLComponents();
514  }
515}
516
517base::string16 OmniboxViewViews::GetText() const {
518  // TODO(oshima): IME support
519  return text();
520}
521
522void OmniboxViewViews::SetUserText(const base::string16& text,
523                                   const base::string16& display_text,
524                                   bool update_popup) {
525  saved_selection_for_focus_change_ = gfx::Range::InvalidRange();
526  OmniboxView::SetUserText(text, display_text, update_popup);
527}
528
529void OmniboxViewViews::SetWindowTextAndCaretPos(const base::string16& text,
530                                                size_t caret_pos,
531                                                bool update_popup,
532                                                bool notify_text_changed) {
533  const gfx::Range range(caret_pos, caret_pos);
534  SetTextAndSelectedRange(text, range);
535
536  if (update_popup)
537    UpdatePopup();
538
539  if (notify_text_changed)
540    TextChanged();
541}
542
543void OmniboxViewViews::SetForcedQuery() {
544  const base::string16 current_text(text());
545  const size_t start = current_text.find_first_not_of(base::kWhitespaceUTF16);
546  if (start == base::string16::npos || (current_text[start] != '?'))
547    OmniboxView::SetUserText(base::ASCIIToUTF16("?"));
548  else
549    SelectRange(gfx::Range(current_text.size(), start + 1));
550}
551
552bool OmniboxViewViews::IsSelectAll() const {
553  // TODO(oshima): IME support.
554  return text() == GetSelectedText();
555}
556
557bool OmniboxViewViews::DeleteAtEndPressed() {
558  return delete_at_end_pressed_;
559}
560
561void OmniboxViewViews::GetSelectionBounds(
562    base::string16::size_type* start,
563    base::string16::size_type* end) const {
564  const gfx::Range range = GetSelectedRange();
565  *start = static_cast<size_t>(range.start());
566  *end = static_cast<size_t>(range.end());
567}
568
569void OmniboxViewViews::SelectAll(bool reversed) {
570  views::Textfield::SelectAll(reversed);
571}
572
573void OmniboxViewViews::RevertAll() {
574  saved_selection_for_focus_change_ = gfx::Range::InvalidRange();
575  OmniboxView::RevertAll();
576}
577
578void OmniboxViewViews::UpdatePopup() {
579  model()->SetInputInProgress(true);
580  if (!model()->has_focus())
581    return;
582
583  // Prevent inline autocomplete when the caret isn't at the end of the text,
584  // and during IME composition editing unless
585  // |kEnableOmniboxAutoCompletionForIme| is enabled.
586  const gfx::Range sel = GetSelectedRange();
587  model()->StartAutocomplete(
588      !sel.is_empty(),
589      sel.GetMax() < text().length() ||
590      (IsIMEComposing() && !IsOmniboxAutoCompletionForImeEnabled()));
591}
592
593void OmniboxViewViews::SetFocus() {
594  RequestFocus();
595  // Restore caret visibility if focus is explicitly requested. This is
596  // necessary because if we already have invisible focus, the RequestFocus()
597  // call above will short-circuit, preventing us from reaching
598  // OmniboxEditModel::OnSetFocus(), which handles restoring visibility when the
599  // omnibox regains focus after losing focus.
600  model()->SetCaretVisibility(true);
601}
602
603void OmniboxViewViews::ApplyCaretVisibility() {
604  SetCursorEnabled(model()->is_caret_visible());
605}
606
607void OmniboxViewViews::OnTemporaryTextMaybeChanged(
608    const base::string16& display_text,
609    bool save_original_selection,
610    bool notify_text_changed) {
611  if (save_original_selection)
612    saved_temporary_selection_ = GetSelectedRange();
613
614  SetWindowTextAndCaretPos(display_text, display_text.length(), false,
615                           notify_text_changed);
616}
617
618bool OmniboxViewViews::OnInlineAutocompleteTextMaybeChanged(
619    const base::string16& display_text,
620    size_t user_text_length) {
621  if (display_text == text())
622    return false;
623
624  if (IsIMEComposing()) {
625    location_bar_view_->SetImeInlineAutocompletion(
626        display_text.substr(user_text_length));
627  } else {
628    gfx::Range range(display_text.size(), user_text_length);
629    SetTextAndSelectedRange(display_text, range);
630  }
631  TextChanged();
632  return true;
633}
634
635void OmniboxViewViews::OnInlineAutocompleteTextCleared() {
636  // Hide the inline autocompletion for IME users.
637  location_bar_view_->SetImeInlineAutocompletion(base::string16());
638}
639
640void OmniboxViewViews::OnRevertTemporaryText() {
641  SelectRange(saved_temporary_selection_);
642  // We got here because the user hit the Escape key. We explicitly don't call
643  // TextChanged(), since OmniboxPopupModel::ResetToDefaultMatch() has already
644  // been called by now, and it would've called TextChanged() if it was
645  // warranted.
646}
647
648void OmniboxViewViews::OnBeforePossibleChange() {
649  // Record our state.
650  text_before_change_ = text();
651  sel_before_change_ = GetSelectedRange();
652  ime_composing_before_change_ = IsIMEComposing();
653}
654
655bool OmniboxViewViews::OnAfterPossibleChange() {
656  // See if the text or selection have changed since OnBeforePossibleChange().
657  const base::string16 new_text = text();
658  const gfx::Range new_sel = GetSelectedRange();
659  const bool text_changed = (new_text != text_before_change_) ||
660      (ime_composing_before_change_ != IsIMEComposing());
661  const bool selection_differs =
662      !((sel_before_change_.is_empty() && new_sel.is_empty()) ||
663        sel_before_change_.EqualsIgnoringDirection(new_sel));
664
665  // When the user has deleted text, we don't allow inline autocomplete.  Make
666  // sure to not flag cases like selecting part of the text and then pasting
667  // (or typing) the prefix of that selection.  (We detect these by making
668  // sure the caret, which should be after any insertion, hasn't moved
669  // forward of the old selection start.)
670  const bool just_deleted_text =
671      (text_before_change_.length() > new_text.length()) &&
672      (new_sel.start() <= sel_before_change_.GetMin());
673
674  const bool something_changed = model()->OnAfterPossibleChange(
675      text_before_change_, new_text, new_sel.start(), new_sel.end(),
676      selection_differs, text_changed, just_deleted_text, !IsIMEComposing());
677
678  // If only selection was changed, we don't need to call model()'s
679  // OnChanged() method, which is called in TextChanged().
680  // But we still need to call EmphasizeURLComponents() to make sure the text
681  // attributes are updated correctly.
682  if (something_changed && text_changed)
683    TextChanged();
684  else if (selection_differs)
685    EmphasizeURLComponents();
686  else if (delete_at_end_pressed_)
687    model()->OnChanged();
688
689  return something_changed;
690}
691
692gfx::NativeView OmniboxViewViews::GetNativeView() const {
693  return GetWidget()->GetNativeView();
694}
695
696gfx::NativeView OmniboxViewViews::GetRelativeWindowForPopup() const {
697  return GetWidget()->GetTopLevelWidget()->GetNativeView();
698}
699
700void OmniboxViewViews::SetGrayTextAutocompletion(const base::string16& input) {
701#if defined(OS_WIN) || defined(USE_AURA)
702  location_bar_view_->SetGrayTextAutocompletion(input);
703#endif
704}
705
706base::string16 OmniboxViewViews::GetGrayTextAutocompletion() const {
707#if defined(OS_WIN) || defined(USE_AURA)
708  return location_bar_view_->GetGrayTextAutocompletion();
709#else
710  return base::string16();
711#endif
712}
713
714int OmniboxViewViews::GetTextWidth() const {
715  // Returns the width necessary to display the current text, including any
716  // necessary space for the cursor or border/margin.
717  return GetRenderText()->GetContentWidth() + GetInsets().width();
718}
719
720int OmniboxViewViews::GetWidth() const {
721  return location_bar_view_->width();
722}
723
724bool OmniboxViewViews::IsImeComposing() const {
725  return IsIMEComposing();
726}
727
728bool OmniboxViewViews::IsImeShowingPopup() const {
729#if defined(OS_CHROMEOS)
730  return ime_candidate_window_open_;
731#else
732  const views::InputMethod* input_method = this->GetInputMethod();
733  return input_method && input_method->IsCandidatePopupOpen();
734#endif
735}
736
737void OmniboxViewViews::ShowImeIfNeeded() {
738  GetInputMethod()->ShowImeIfNeeded();
739}
740
741bool OmniboxViewViews::IsCommandIdEnabled(int command_id) const {
742  if (command_id == IDS_APP_PASTE)
743    return !read_only() && !GetClipboardText().empty();
744  if (command_id == IDS_PASTE_AND_GO)
745    return !read_only() && model()->CanPasteAndGo(GetClipboardText());
746  if (command_id == IDS_SHOW_URL)
747    return controller()->GetToolbarModel()->WouldReplaceURL();
748  return Textfield::IsCommandIdEnabled(command_id) ||
749         command_updater()->IsCommandEnabled(command_id);
750}
751
752bool OmniboxViewViews::IsItemForCommandIdDynamic(int command_id) const {
753  return command_id == IDS_PASTE_AND_GO;
754}
755
756base::string16 OmniboxViewViews::GetLabelForCommandId(int command_id) const {
757  DCHECK_EQ(IDS_PASTE_AND_GO, command_id);
758  return l10n_util::GetStringUTF16(
759      model()->IsPasteAndSearch(GetClipboardText()) ?
760          IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO);
761}
762
763void OmniboxViewViews::ExecuteCommand(int command_id, int event_flags) {
764  switch (command_id) {
765    // These commands don't invoke the popup via OnBefore/AfterPossibleChange().
766    case IDS_PASTE_AND_GO:
767      model()->PasteAndGo(GetClipboardText());
768      break;
769    case IDS_SHOW_URL:
770      ShowURL();
771      break;
772    case IDC_EDIT_SEARCH_ENGINES:
773      command_updater()->ExecuteCommand(command_id);
774      break;
775
776    default:
777      OnBeforePossibleChange();
778      if (command_id == IDS_APP_PASTE)
779        OnPaste();
780      else if (Textfield::IsCommandIdEnabled(command_id))
781        Textfield::ExecuteCommand(command_id, event_flags);
782      else
783        command_updater()->ExecuteCommand(command_id);
784      OnAfterPossibleChange();
785      break;
786  }
787}
788
789////////////////////////////////////////////////////////////////////////////////
790// OmniboxViewViews, views::TextfieldController implementation:
791
792void OmniboxViewViews::ContentsChanged(views::Textfield* sender,
793                                       const base::string16& new_contents) {
794}
795
796bool OmniboxViewViews::HandleKeyEvent(views::Textfield* textfield,
797                                      const ui::KeyEvent& event) {
798  delete_at_end_pressed_ = false;
799
800  if (event.key_code() == ui::VKEY_BACK) {
801    // No extra handling is needed in keyword search mode, if there is a
802    // non-empty selection, or if the cursor is not leading the text.
803    if (model()->is_keyword_hint() || model()->keyword().empty() ||
804        HasSelection() || GetCursorPosition() != 0)
805      return false;
806    model()->ClearKeyword(text());
807    return true;
808  }
809
810  if (event.key_code() == ui::VKEY_DELETE && !event.IsAltDown()) {
811    delete_at_end_pressed_ =
812        (!HasSelection() && GetCursorPosition() == text().length());
813  }
814
815  // Handle the right-arrow key for LTR text and the left-arrow key for RTL text
816  // if there is gray text that needs to be committed.
817  if (GetCursorPosition() == text().length()) {
818    base::i18n::TextDirection direction = GetTextDirection();
819    if ((direction == base::i18n::LEFT_TO_RIGHT &&
820         event.key_code() == ui::VKEY_RIGHT) ||
821        (direction == base::i18n::RIGHT_TO_LEFT &&
822         event.key_code() == ui::VKEY_LEFT)) {
823      return model()->CommitSuggestedText();
824    }
825  }
826
827  return false;
828}
829
830void OmniboxViewViews::OnBeforeUserAction(views::Textfield* sender) {
831  OnBeforePossibleChange();
832}
833
834void OmniboxViewViews::OnAfterUserAction(views::Textfield* sender) {
835  OnAfterPossibleChange();
836}
837
838void OmniboxViewViews::OnAfterCutOrCopy() {
839  ui::Clipboard* cb = ui::Clipboard::GetForCurrentThread();
840  base::string16 selected_text;
841  cb->ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE, &selected_text);
842  GURL url;
843  bool write_url;
844  model()->AdjustTextForCopy(GetSelectedRange().GetMin(), IsSelectAll(),
845                             &selected_text, &url, &write_url);
846  if (IsSelectAll())
847    UMA_HISTOGRAM_COUNTS(OmniboxEditModel::kCutOrCopyAllTextHistogram, 1);
848
849  if (write_url) {
850    BookmarkNodeData data;
851    data.ReadFromTuple(url, selected_text);
852    data.WriteToClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE);
853  } else {
854    ui::ScopedClipboardWriter scoped_clipboard_writer(
855        ui::Clipboard::GetForCurrentThread(), ui::CLIPBOARD_TYPE_COPY_PASTE);
856    scoped_clipboard_writer.WriteText(selected_text);
857  }
858}
859
860void OmniboxViewViews::OnGetDragOperationsForTextfield(int* drag_operations) {
861  base::string16 selected_text = GetSelectedText();
862  GURL url;
863  bool write_url;
864  model()->AdjustTextForCopy(GetSelectedRange().GetMin(), IsSelectAll(),
865                             &selected_text, &url, &write_url);
866  if (write_url)
867    *drag_operations |= ui::DragDropTypes::DRAG_LINK;
868}
869
870void OmniboxViewViews::OnWriteDragData(ui::OSExchangeData* data) {
871  base::string16 selected_text = GetSelectedText();
872  GURL url;
873  bool write_url;
874  bool is_all_selected = IsSelectAll();
875  model()->AdjustTextForCopy(GetSelectedRange().GetMin(), is_all_selected,
876                             &selected_text, &url, &write_url);
877  data->SetString(selected_text);
878  if (write_url) {
879    gfx::Image favicon;
880    base::string16 title = selected_text;
881    if (is_all_selected)
882      model()->GetDataForURLExport(&url, &title, &favicon);
883    button_drag_utils::SetURLAndDragImage(url, title, favicon.AsImageSkia(),
884                                          data, GetWidget());
885    data->SetURL(url, title);
886  }
887}
888
889void OmniboxViewViews::AppendDropFormats(
890    int* formats,
891    std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
892  *formats = *formats | ui::OSExchangeData::URL;
893}
894
895int OmniboxViewViews::OnDrop(const ui::OSExchangeData& data) {
896  if (HasTextBeingDragged())
897    return ui::DragDropTypes::DRAG_NONE;
898
899  if (data.HasURL()) {
900    GURL url;
901    base::string16 title;
902    if (data.GetURLAndTitle(
903            ui::OSExchangeData::CONVERT_FILENAMES, &url, &title)) {
904      base::string16 text(
905          StripJavascriptSchemas(base::UTF8ToUTF16(url.spec())));
906      if (model()->CanPasteAndGo(text)) {
907        model()->PasteAndGo(text);
908        return ui::DragDropTypes::DRAG_COPY;
909      }
910    }
911  } else if (data.HasString()) {
912    base::string16 text;
913    if (data.GetString(&text)) {
914      base::string16 collapsed_text(CollapseWhitespace(text, true));
915      if (model()->CanPasteAndGo(collapsed_text))
916        model()->PasteAndGo(collapsed_text);
917      return ui::DragDropTypes::DRAG_COPY;
918    }
919  }
920
921  return ui::DragDropTypes::DRAG_NONE;
922}
923
924void OmniboxViewViews::UpdateContextMenu(ui::SimpleMenuModel* menu_contents) {
925  int paste_position = menu_contents->GetIndexOfCommandId(IDS_APP_PASTE);
926  DCHECK_GE(paste_position, 0);
927  menu_contents->InsertItemWithStringIdAt(
928      paste_position + 1, IDS_PASTE_AND_GO, IDS_PASTE_AND_GO);
929
930  menu_contents->AddSeparator(ui::NORMAL_SEPARATOR);
931
932  if (chrome::IsQueryExtractionEnabled() || chrome::ShouldDisplayOriginChip() ||
933      chrome::ShouldDisplayOriginChipV2()) {
934    int select_all_position = menu_contents->GetIndexOfCommandId(
935        IDS_APP_SELECT_ALL);
936    DCHECK_GE(select_all_position, 0);
937    menu_contents->InsertItemWithStringIdAt(
938        select_all_position + 1, IDS_SHOW_URL, IDS_SHOW_URL);
939  }
940
941  // Minor note: We use IDC_ for command id here while the underlying textfield
942  // is using IDS_ for all its command ids. This is because views cannot depend
943  // on IDC_ for now.
944  menu_contents->AddItemWithStringId(IDC_EDIT_SEARCH_ENGINES,
945      IDS_EDIT_SEARCH_ENGINES);
946}
947
948#if defined(OS_CHROMEOS)
949void OmniboxViewViews::CandidateWindowOpened(
950      chromeos::input_method::InputMethodManager* manager) {
951  ime_candidate_window_open_ = true;
952}
953
954void OmniboxViewViews::CandidateWindowClosed(
955      chromeos::input_method::InputMethodManager* manager) {
956  ime_candidate_window_open_ = false;
957}
958#endif
959
960////////////////////////////////////////////////////////////////////////////////
961// OmniboxViewViews, private:
962
963int OmniboxViewViews::GetOmniboxTextLength() const {
964  // TODO(oshima): Support IME.
965  return static_cast<int>(text().length());
966}
967
968void OmniboxViewViews::EmphasizeURLComponents() {
969  // See whether the contents are a URL with a non-empty host portion, which we
970  // should emphasize.  To check for a URL, rather than using the type returned
971  // by Parse(), ask the model, which will check the desired page transition for
972  // this input.  This can tell us whether an UNKNOWN input string is going to
973  // be treated as a search or a navigation, and is the same method the Paste
974  // And Go system uses.
975  url_parse::Component scheme, host;
976  AutocompleteInput::ParseForEmphasizeComponents(text(), &scheme, &host);
977  bool grey_out_url = text().substr(scheme.begin, scheme.len) ==
978      base::UTF8ToUTF16(extensions::kExtensionScheme);
979  bool grey_base = model()->CurrentTextIsURL() &&
980      (host.is_nonempty() || grey_out_url);
981  SetColor(location_bar_view_->GetColor(
982      security_level_,
983      grey_base ? LocationBarView::DEEMPHASIZED_TEXT : LocationBarView::TEXT));
984  if (grey_base && !grey_out_url) {
985    ApplyColor(
986        location_bar_view_->GetColor(security_level_, LocationBarView::TEXT),
987        gfx::Range(host.begin, host.end()));
988  }
989
990  // Emphasize the scheme for security UI display purposes (if necessary).
991  // Note that we check CurrentTextIsURL() because if we're replacing search
992  // URLs with search terms, we may have a non-URL even when the user is not
993  // editing; and in some cases, e.g. for "site:foo.com" searches, the parser
994  // may have incorrectly identified a qualifier as a scheme.
995  SetStyle(gfx::DIAGONAL_STRIKE, false);
996  if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() &&
997      scheme.is_nonempty() && (security_level_ != ToolbarModel::NONE)) {
998    SkColor security_color = location_bar_view_->GetColor(
999        security_level_, LocationBarView::SECURITY_TEXT);
1000    const bool strike = (security_level_ == ToolbarModel::SECURITY_ERROR);
1001    const gfx::Range scheme_range(scheme.begin, scheme.end());
1002    ApplyColor(security_color, scheme_range);
1003    ApplyStyle(gfx::DIAGONAL_STRIKE, strike, scheme_range);
1004  }
1005}
1006
1007void OmniboxViewViews::SetTextAndSelectedRange(const base::string16& text,
1008                                               const gfx::Range& range) {
1009  SetText(text);
1010  SelectRange(range);
1011}
1012
1013base::string16 OmniboxViewViews::GetSelectedText() const {
1014  // TODO(oshima): Support IME.
1015  return views::Textfield::GetSelectedText();
1016}
1017
1018void OmniboxViewViews::OnPaste() {
1019  const base::string16 text(GetClipboardText());
1020  if (!text.empty()) {
1021    // Record this paste, so we can do different behavior.
1022    model()->OnPaste();
1023    // Force a Paste operation to trigger the text_changed code in
1024    // OnAfterPossibleChange(), even if identical contents are pasted.
1025    text_before_change_.clear();
1026    InsertOrReplaceText(text);
1027  }
1028}
1029