1// Copyright (c) 2011 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/autocomplete/autocomplete_edit_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_edit.h"
12#include "chrome/browser/autocomplete/autocomplete_match.h"
13#include "chrome/browser/autocomplete/autocomplete_popup_model.h"
14#include "chrome/browser/command_updater.h"
15#include "chrome/browser/ui/views/autocomplete/autocomplete_popup_contents_view.h"
16#include "chrome/browser/ui/views/autocomplete/touch_autocomplete_popup_contents_view.h"
17#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
18#include "content/browser/tab_contents/tab_contents.h"
19#include "content/common/notification_service.h"
20#include "googleurl/src/gurl.h"
21#include "grit/generated_resources.h"
22#include "net/base/escape.h"
23#include "ui/base/accessibility/accessible_view_state.h"
24#include "ui/base/dragdrop/drag_drop_types.h"
25#include "ui/base/l10n/l10n_util.h"
26#include "ui/base/resource/resource_bundle.h"
27#include "ui/gfx/font.h"
28#include "views/border.h"
29#include "views/controls/textfield/textfield.h"
30#include "views/layout/fill_layout.h"
31
32namespace {
33
34// Textfield for autocomplete that intercepts events that are necessary
35// for AutocompleteEditViewViews.
36class AutocompleteTextfield : public views::Textfield {
37 public:
38  explicit AutocompleteTextfield(
39      AutocompleteEditViewViews* autocomplete_edit_view)
40      : views::Textfield(views::Textfield::STYLE_DEFAULT),
41        autocomplete_edit_view_(autocomplete_edit_view) {
42    DCHECK(autocomplete_edit_view_);
43    RemoveBorder();
44  }
45
46  // views::View implementation
47  virtual void OnFocus() OVERRIDE {
48    views::Textfield::OnFocus();
49    autocomplete_edit_view_->HandleFocusIn();
50  }
51
52  virtual void OnBlur() OVERRIDE {
53    views::Textfield::OnBlur();
54    autocomplete_edit_view_->HandleFocusOut();
55  }
56
57  virtual bool OnKeyPressed(const views::KeyEvent& event) OVERRIDE {
58    bool handled = views::Textfield::OnKeyPressed(event);
59    return autocomplete_edit_view_->HandleAfterKeyEvent(event, handled) ||
60        handled;
61  }
62
63  virtual bool OnKeyReleased(const views::KeyEvent& event) OVERRIDE {
64    return autocomplete_edit_view_->HandleKeyReleaseEvent(event);
65  }
66
67  virtual bool IsFocusable() const OVERRIDE {
68    // Bypass Textfield::IsFocusable. The omnibox in popup window requires
69    // focus in order for text selection to work.
70    return views::View::IsFocusable();
71  }
72
73 private:
74  AutocompleteEditViewViews* autocomplete_edit_view_;
75
76  DISALLOW_COPY_AND_ASSIGN(AutocompleteTextfield);
77};
78
79// Stores omnibox state for each tab.
80struct ViewState {
81  explicit ViewState(const ui::Range& selection_range)
82      : selection_range(selection_range) {
83  }
84
85  // Range of selected text.
86  ui::Range selection_range;
87};
88
89struct AutocompleteEditState {
90  AutocompleteEditState(const AutocompleteEditModel::State& model_state,
91                        const ViewState& view_state)
92      : model_state(model_state),
93        view_state(view_state) {
94  }
95
96  const AutocompleteEditModel::State model_state;
97  const ViewState view_state;
98};
99
100// Returns a lazily initialized property bag accessor for saving our state in a
101// TabContents.
102PropertyAccessor<AutocompleteEditState>* GetStateAccessor() {
103  static PropertyAccessor<AutocompleteEditState> state;
104  return &state;
105}
106
107const int kAutocompleteVerticalMargin = 4;
108
109}  // namespace
110
111AutocompleteEditViewViews::AutocompleteEditViewViews(
112    AutocompleteEditController* controller,
113    ToolbarModel* toolbar_model,
114    Profile* profile,
115    CommandUpdater* command_updater,
116    bool popup_window_mode,
117    const views::View* location_bar)
118    : model_(new AutocompleteEditModel(this, controller, profile)),
119      popup_view_(CreatePopupView(profile, location_bar)),
120      controller_(controller),
121      toolbar_model_(toolbar_model),
122      command_updater_(command_updater),
123      popup_window_mode_(popup_window_mode),
124      security_level_(ToolbarModel::NONE),
125      ime_composing_before_change_(false),
126      delete_at_end_pressed_(false) {
127  set_border(views::Border::CreateEmptyBorder(kAutocompleteVerticalMargin, 0,
128                                              kAutocompleteVerticalMargin, 0));
129}
130
131AutocompleteEditViewViews::~AutocompleteEditViewViews() {
132  NotificationService::current()->Notify(
133      NotificationType::AUTOCOMPLETE_EDIT_DESTROYED,
134      Source<AutocompleteEditViewViews>(this),
135      NotificationService::NoDetails());
136  // Explicitly teardown members which have a reference to us.  Just to be safe
137  // we want them to be destroyed before destroying any other internal state.
138  popup_view_.reset();
139  model_.reset();
140}
141
142////////////////////////////////////////////////////////////////////////////////
143// AutocompleteEditViewViews public:
144
145void AutocompleteEditViewViews::Init() {
146  // The height of the text view is going to change based on the font used.  We
147  // don't want to stretch the height, and we want it vertically centered.
148  // TODO(oshima): make sure the above happens with views.
149  textfield_ = new AutocompleteTextfield(this);
150  textfield_->SetController(this);
151
152#if defined(TOUCH_UI)
153  textfield_->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont(
154                      ResourceBundle::LargeFont));
155#endif
156
157  if (popup_window_mode_)
158    textfield_->SetReadOnly(true);
159
160  // Manually invoke SetBaseColor() because TOOLKIT_VIEWS doesn't observe
161  // themes.
162  SetBaseColor();
163}
164
165void AutocompleteEditViewViews::SetBaseColor() {
166  // TODO(oshima): Implment style change.
167  NOTIMPLEMENTED();
168}
169
170bool AutocompleteEditViewViews::HandleAfterKeyEvent(
171    const views::KeyEvent& event,
172    bool handled) {
173  if (event.key_code() == ui::VKEY_RETURN) {
174    bool alt_held = event.IsAltDown();
175    model_->AcceptInput(alt_held ? NEW_FOREGROUND_TAB : CURRENT_TAB, false);
176    handled = true;
177  } else if (!handled && event.key_code() == ui::VKEY_ESCAPE) {
178    // We can handle the Escape key if textfield did not handle it.
179    // If it's not handled by us, then we need to propagate it up to the parent
180    // widgets, so that Escape accelerator can still work.
181    handled = model_->OnEscapeKeyPressed();
182  } else if (event.key_code() == ui::VKEY_CONTROL) {
183    // Omnibox2 can switch its contents while pressing a control key. To switch
184    // the contents of omnibox2, we notify the AutocompleteEditModel class when
185    // the control-key state is changed.
186    model_->OnControlKeyChanged(true);
187  } else if (!handled && event.key_code() == ui::VKEY_DELETE &&
188             event.IsShiftDown()) {
189    // If shift+del didn't change the text, we let this delete an entry from
190    // the popup.  We can't check to see if the IME handled it because even if
191    // nothing is selected, the IME or the TextView still report handling it.
192    if (model_->popup_model()->IsOpen())
193      model_->popup_model()->TryDeletingCurrentItem();
194  } else if (!handled && event.key_code() == ui::VKEY_UP) {
195    model_->OnUpOrDownKeyPressed(-1);
196    handled = true;
197  } else if (!handled && event.key_code() == ui::VKEY_DOWN) {
198    model_->OnUpOrDownKeyPressed(1);
199    handled = true;
200  } else if (!handled &&
201             event.key_code() == ui::VKEY_TAB &&
202             !event.IsShiftDown() &&
203             !event.IsControlDown()) {
204    if (model_->is_keyword_hint()) {
205      handled = model_->AcceptKeyword();
206    } else {
207      string16::size_type start = 0;
208      string16::size_type end = 0;
209      size_t length = GetTextLength();
210      GetSelectionBounds(&start, &end);
211      if (start != end || start < length) {
212        OnBeforePossibleChange();
213        SelectRange(length, length);
214        OnAfterPossibleChange();
215        handled = true;
216      }
217
218      // TODO(Oshima): handle instant
219    }
220  }
221  // TODO(oshima): page up & down
222
223  return handled;
224}
225
226bool AutocompleteEditViewViews::HandleKeyReleaseEvent(
227    const views::KeyEvent& event) {
228  // Omnibox2 can switch its contents while pressing a control key. To switch
229  // the contents of omnibox2, we notify the AutocompleteEditModel class when
230  // the control-key state is changed.
231  if (event.key_code() == ui::VKEY_CONTROL) {
232    // TODO(oshima): investigate if we need to support keyboard with two
233    // controls. See autocomplete_edit_view_gtk.cc.
234    model_->OnControlKeyChanged(false);
235    return true;
236  }
237  return false;
238}
239
240void AutocompleteEditViewViews::HandleFocusIn() {
241  // TODO(oshima): Get control key state.
242  model_->OnSetFocus(false);
243  // Don't call controller_->OnSetFocus as this view has already
244  // acquired the focus.
245}
246
247void AutocompleteEditViewViews::HandleFocusOut() {
248  // TODO(oshima): we don't have native view. This requires
249  // further refactoring.
250  model_->OnWillKillFocus(NULL);
251  // Close the popup.
252  ClosePopup();
253  // Tell the model to reset itself.
254  model_->OnKillFocus();
255  controller_->OnKillFocus();
256}
257
258////////////////////////////////////////////////////////////////////////////////
259// AutocompleteEditViewViews, views::View implementation:
260void AutocompleteEditViewViews::Layout() {
261  gfx::Insets insets = GetInsets();
262  textfield_->SetBounds(insets.left(), insets.top(),
263                        width() - insets.width(),
264                        height() - insets.height());
265}
266
267void AutocompleteEditViewViews::GetAccessibleState(
268    ui::AccessibleViewState* state) {
269  state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_LOCATION);
270}
271
272////////////////////////////////////////////////////////////////////////////////
273// AutocompleteEditViewViews, AutocopmleteEditView implementation:
274
275AutocompleteEditModel* AutocompleteEditViewViews::model() {
276  return model_.get();
277}
278
279const AutocompleteEditModel* AutocompleteEditViewViews::model() const {
280  return model_.get();
281}
282
283void AutocompleteEditViewViews::SaveStateToTab(TabContents* tab) {
284  DCHECK(tab);
285
286  // NOTE: GetStateForTabSwitch may affect GetSelection, so order is important.
287  AutocompleteEditModel::State model_state = model_->GetStateForTabSwitch();
288  ui::Range selection;
289  textfield_->GetSelectedRange(&selection);
290  GetStateAccessor()->SetProperty(
291      tab->property_bag(),
292      AutocompleteEditState(model_state, ViewState(selection)));
293}
294
295void AutocompleteEditViewViews::Update(const TabContents* contents) {
296  // NOTE: We're getting the URL text here from the ToolbarModel.
297  bool visibly_changed_permanent_text =
298      model_->UpdatePermanentText(WideToUTF16Hack(toolbar_model_->GetText()));
299
300  ToolbarModel::SecurityLevel security_level =
301        toolbar_model_->GetSecurityLevel();
302  bool changed_security_level = (security_level != security_level_);
303  security_level_ = security_level;
304
305  // TODO(oshima): Copied from gtk implementation which is
306  // slightly different from WIN impl. Find out the correct implementation
307  // for views-implementation.
308  if (contents) {
309    RevertAll();
310    const AutocompleteEditState* state =
311        GetStateAccessor()->GetProperty(contents->property_bag());
312    if (state) {
313      model_->RestoreState(state->model_state);
314
315      // Move the marks for the cursor and the other end of the selection to
316      // the previously-saved offsets (but preserve PRIMARY).
317      textfield_->SelectRange(state->view_state.selection_range);
318    }
319  } else if (visibly_changed_permanent_text) {
320    RevertAll();
321  } else if (changed_security_level) {
322    EmphasizeURLComponents();
323  }
324}
325
326void AutocompleteEditViewViews::OpenURL(const GURL& url,
327                                        WindowOpenDisposition disposition,
328                                        PageTransition::Type transition,
329                                        const GURL& alternate_nav_url,
330                                        size_t selected_line,
331                                        const string16& keyword) {
332  if (!url.is_valid())
333    return;
334
335  model_->OpenURL(url, disposition, transition, alternate_nav_url,
336                  selected_line, keyword);
337}
338
339string16 AutocompleteEditViewViews::GetText() const {
340  // TODO(oshima): IME support
341  return textfield_->text();
342}
343
344bool AutocompleteEditViewViews::IsEditingOrEmpty() const {
345  return model_->user_input_in_progress() || (GetTextLength() == 0);
346}
347
348int AutocompleteEditViewViews::GetIcon() const {
349  return IsEditingOrEmpty() ?
350      AutocompleteMatch::TypeToIcon(model_->CurrentTextType()) :
351      toolbar_model_->GetIcon();
352}
353
354void AutocompleteEditViewViews::SetUserText(const string16& text) {
355  SetUserText(text, text, true);
356}
357
358void AutocompleteEditViewViews::SetUserText(const string16& text,
359                                            const string16& display_text,
360                                            bool update_popup) {
361  model_->SetUserText(text);
362  SetWindowTextAndCaretPos(display_text, display_text.length());
363  if (update_popup)
364    UpdatePopup();
365  TextChanged();
366}
367
368void AutocompleteEditViewViews::SetWindowTextAndCaretPos(
369    const string16& text,
370    size_t caret_pos) {
371  const ui::Range range(caret_pos, caret_pos);
372  SetTextAndSelectedRange(text, range);
373}
374
375void AutocompleteEditViewViews::SetForcedQuery() {
376  const string16 current_text(GetText());
377  const size_t start = current_text.find_first_not_of(kWhitespaceUTF16);
378  if (start == string16::npos || (current_text[start] != '?')) {
379    SetUserText(ASCIIToUTF16("?"));
380  } else {
381    SelectRange(current_text.size(), start + 1);
382  }
383}
384
385bool AutocompleteEditViewViews::IsSelectAll() {
386  // TODO(oshima): IME support.
387  return textfield_->text() == textfield_->GetSelectedText();
388}
389
390bool AutocompleteEditViewViews::DeleteAtEndPressed() {
391  return delete_at_end_pressed_;
392}
393
394void AutocompleteEditViewViews::GetSelectionBounds(
395    string16::size_type* start,
396    string16::size_type* end) {
397  ui::Range range;
398  textfield_->GetSelectedRange(&range);
399  *start = static_cast<size_t>(range.end());
400  *end = static_cast<size_t>(range.start());
401}
402
403void AutocompleteEditViewViews::SelectAll(bool reversed) {
404  if (reversed)
405    SelectRange(GetTextLength(), 0);
406  else
407    SelectRange(0, GetTextLength());
408}
409
410void AutocompleteEditViewViews::RevertAll() {
411  ClosePopup();
412  model_->Revert();
413  TextChanged();
414}
415
416void AutocompleteEditViewViews::UpdatePopup() {
417  model_->SetInputInProgress(true);
418  if (!model_->has_focus())
419    return;
420
421  // Don't inline autocomplete when the caret/selection isn't at the end of
422  // the text, or in the middle of composition.
423  ui::Range sel;
424  textfield_->GetSelectedRange(&sel);
425  bool no_inline_autocomplete =
426      sel.GetMax() < GetTextLength() || textfield_->IsIMEComposing();
427
428  model_->StartAutocomplete(!sel.is_empty(), no_inline_autocomplete);
429}
430
431void AutocompleteEditViewViews::ClosePopup() {
432  model_->StopAutocomplete();
433}
434
435void AutocompleteEditViewViews::SetFocus() {
436  // In views-implementation, the focus is on textfield rather than
437  // AutocompleteEditView.
438  textfield_->RequestFocus();
439}
440
441void AutocompleteEditViewViews::OnTemporaryTextMaybeChanged(
442    const string16& display_text,
443    bool save_original_selection) {
444  if (save_original_selection)
445    textfield_->GetSelectedRange(&saved_temporary_selection_);
446
447  SetWindowTextAndCaretPos(display_text, display_text.length());
448  TextChanged();
449}
450
451bool AutocompleteEditViewViews::OnInlineAutocompleteTextMaybeChanged(
452    const string16& display_text,
453    size_t user_text_length) {
454  if (display_text == GetText())
455    return false;
456  ui::Range range(display_text.size(), user_text_length);
457  SetTextAndSelectedRange(display_text, range);
458  TextChanged();
459  return true;
460}
461
462void AutocompleteEditViewViews::OnRevertTemporaryText() {
463  textfield_->SelectRange(saved_temporary_selection_);
464  TextChanged();
465}
466
467void AutocompleteEditViewViews::OnBeforePossibleChange() {
468  // Record our state.
469  text_before_change_ = GetText();
470  textfield_->GetSelectedRange(&sel_before_change_);
471  ime_composing_before_change_ = textfield_->IsIMEComposing();
472}
473
474bool AutocompleteEditViewViews::OnAfterPossibleChange() {
475  ui::Range new_sel;
476  textfield_->GetSelectedRange(&new_sel);
477
478  // See if the text or selection have changed since OnBeforePossibleChange().
479  const string16 new_text = GetText();
480  const bool text_changed = (new_text != text_before_change_) ||
481      (ime_composing_before_change_ != textfield_->IsIMEComposing());
482  const bool selection_differs =
483      !((sel_before_change_.is_empty() && new_sel.is_empty()) ||
484        sel_before_change_.EqualsIgnoringDirection(new_sel));
485
486  // When the user has deleted text, we don't allow inline autocomplete.  Make
487  // sure to not flag cases like selecting part of the text and then pasting
488  // (or typing) the prefix of that selection.  (We detect these by making
489  // sure the caret, which should be after any insertion, hasn't moved
490  // forward of the old selection start.)
491  const bool just_deleted_text =
492      (text_before_change_.length() > new_text.length()) &&
493      (new_sel.start() <= sel_before_change_.GetMin());
494
495  const bool something_changed = model_->OnAfterPossibleChange(
496      new_text, new_sel.start(), new_sel.end(), selection_differs,
497      text_changed, just_deleted_text, !textfield_->IsIMEComposing());
498
499  // If only selection was changed, we don't need to call |model_|'s
500  // OnChanged() method, which is called in TextChanged().
501  // But we still need to call EmphasizeURLComponents() to make sure the text
502  // attributes are updated correctly.
503  if (something_changed && text_changed)
504    TextChanged();
505  else if (selection_differs)
506    EmphasizeURLComponents();
507  else if (delete_at_end_pressed_)
508    model_->OnChanged();
509
510  return something_changed;
511}
512
513gfx::NativeView AutocompleteEditViewViews::GetNativeView() const {
514  return GetWidget()->GetNativeView();
515}
516
517CommandUpdater* AutocompleteEditViewViews::GetCommandUpdater() {
518  return command_updater_;
519}
520
521void AutocompleteEditViewViews::SetInstantSuggestion(const string16& input,
522                                                     bool animate_to_complete) {
523  NOTIMPLEMENTED();
524}
525
526string16 AutocompleteEditViewViews::GetInstantSuggestion() const {
527  NOTIMPLEMENTED();
528  return string16();
529}
530
531int AutocompleteEditViewViews::TextWidth() const {
532  // TODO(oshima): add horizontal margin.
533  return textfield_->font().GetStringWidth(textfield_->text());
534}
535
536bool AutocompleteEditViewViews::IsImeComposing() const {
537  return false;
538}
539
540views::View* AutocompleteEditViewViews::AddToView(views::View* parent) {
541  parent->AddChildView(this);
542  AddChildView(textfield_);
543  return this;
544}
545
546int AutocompleteEditViewViews::OnPerformDrop(
547    const views::DropTargetEvent& event) {
548  NOTIMPLEMENTED();
549  return ui::DragDropTypes::DRAG_NONE;
550}
551
552////////////////////////////////////////////////////////////////////////////////
553// AutocompleteEditViewViews, NotificationObserver implementation:
554
555void AutocompleteEditViewViews::Observe(NotificationType type,
556                                      const NotificationSource& source,
557                                      const NotificationDetails& details) {
558  DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
559  SetBaseColor();
560}
561
562////////////////////////////////////////////////////////////////////////////////
563// AutocompleteEditViewViews, views::TextfieldController implementation:
564
565void AutocompleteEditViewViews::ContentsChanged(views::Textfield* sender,
566                                                const string16& new_contents) {
567}
568
569bool AutocompleteEditViewViews::HandleKeyEvent(
570    views::Textfield* textfield,
571    const views::KeyEvent& event) {
572  delete_at_end_pressed_ = false;
573
574  if (event.key_code() == ui::VKEY_BACK) {
575    // Checks if it's currently in keyword search mode.
576    if (model_->is_keyword_hint() || model_->keyword().empty())
577      return false;
578    // If there is selection, let textfield handle the backspace.
579    if (textfield_->HasSelection())
580      return false;
581    // If not at the begining of the text, let textfield handle the backspace.
582    if (textfield_->GetCursorPosition())
583      return false;
584    model_->ClearKeyword(GetText());
585    return true;
586  }
587
588  if (event.key_code() == ui::VKEY_DELETE && !event.IsAltDown()) {
589    delete_at_end_pressed_ =
590        (!textfield_->HasSelection() &&
591         textfield_->GetCursorPosition() == textfield_->text().length());
592  }
593
594  return false;
595}
596
597void AutocompleteEditViewViews::OnBeforeUserAction(views::Textfield* sender) {
598  OnBeforePossibleChange();
599}
600
601void AutocompleteEditViewViews::OnAfterUserAction(views::Textfield* sender) {
602  OnAfterPossibleChange();
603}
604
605////////////////////////////////////////////////////////////////////////////////
606// AutocompleteEditViewViews, private:
607
608size_t AutocompleteEditViewViews::GetTextLength() const {
609  // TODO(oshima): Support instant, IME.
610  return textfield_->text().length();
611}
612
613void AutocompleteEditViewViews::EmphasizeURLComponents() {
614  // TODO(oshima): Update URL visual style
615  NOTIMPLEMENTED();
616}
617
618void AutocompleteEditViewViews::TextChanged() {
619  EmphasizeURLComponents();
620  model_->OnChanged();
621}
622
623void AutocompleteEditViewViews::SetTextAndSelectedRange(
624    const string16& text,
625    const ui::Range& range) {
626  if (text != GetText())
627    textfield_->SetText(text);
628  textfield_->SelectRange(range);
629}
630
631string16 AutocompleteEditViewViews::GetSelectedText() const {
632  // TODO(oshima): Support instant, IME.
633  return textfield_->GetSelectedText();
634}
635
636void AutocompleteEditViewViews::SelectRange(size_t caret, size_t end) {
637  const ui::Range range(caret, end);
638  textfield_->SelectRange(range);
639}
640
641AutocompletePopupView* AutocompleteEditViewViews::CreatePopupView(
642    Profile* profile,
643    const View* location_bar) {
644#if defined(TOUCH_UI)
645  return new TouchAutocompletePopupContentsView(
646#else
647  return new AutocompletePopupContentsView(
648#endif
649      gfx::Font(), this, model_.get(), profile, location_bar);
650}
651