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