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 32 namespace { 33 34 // Textfield for autocomplete that intercepts events that are necessary 35 // for AutocompleteEditViewViews. 36 class 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. 80 struct 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 89 struct 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. 102 PropertyAccessor<AutocompleteEditState>* GetStateAccessor() { 103 static PropertyAccessor<AutocompleteEditState> state; 104 return &state; 105 } 106 107 const int kAutocompleteVerticalMargin = 4; 108 109 } // namespace 110 111 AutocompleteEditViewViews::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 131 AutocompleteEditViewViews::~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 145 void 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 165 void AutocompleteEditViewViews::SetBaseColor() { 166 // TODO(oshima): Implment style change. 167 NOTIMPLEMENTED(); 168 } 169 170 bool 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 226 bool 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 240 void 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 247 void 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: 260 void AutocompleteEditViewViews::Layout() { 261 gfx::Insets insets = GetInsets(); 262 textfield_->SetBounds(insets.left(), insets.top(), 263 width() - insets.width(), 264 height() - insets.height()); 265 } 266 267 void AutocompleteEditViewViews::GetAccessibleState( 268 ui::AccessibleViewState* state) { 269 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_LOCATION); 270 } 271 272 //////////////////////////////////////////////////////////////////////////////// 273 // AutocompleteEditViewViews, AutocopmleteEditView implementation: 274 275 AutocompleteEditModel* AutocompleteEditViewViews::model() { 276 return model_.get(); 277 } 278 279 const AutocompleteEditModel* AutocompleteEditViewViews::model() const { 280 return model_.get(); 281 } 282 283 void 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 295 void 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 326 void 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 339 string16 AutocompleteEditViewViews::GetText() const { 340 // TODO(oshima): IME support 341 return textfield_->text(); 342 } 343 344 bool AutocompleteEditViewViews::IsEditingOrEmpty() const { 345 return model_->user_input_in_progress() || (GetTextLength() == 0); 346 } 347 348 int AutocompleteEditViewViews::GetIcon() const { 349 return IsEditingOrEmpty() ? 350 AutocompleteMatch::TypeToIcon(model_->CurrentTextType()) : 351 toolbar_model_->GetIcon(); 352 } 353 354 void AutocompleteEditViewViews::SetUserText(const string16& text) { 355 SetUserText(text, text, true); 356 } 357 358 void 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 368 void 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 375 void 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 385 bool AutocompleteEditViewViews::IsSelectAll() { 386 // TODO(oshima): IME support. 387 return textfield_->text() == textfield_->GetSelectedText(); 388 } 389 390 bool AutocompleteEditViewViews::DeleteAtEndPressed() { 391 return delete_at_end_pressed_; 392 } 393 394 void 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 403 void AutocompleteEditViewViews::SelectAll(bool reversed) { 404 if (reversed) 405 SelectRange(GetTextLength(), 0); 406 else 407 SelectRange(0, GetTextLength()); 408 } 409 410 void AutocompleteEditViewViews::RevertAll() { 411 ClosePopup(); 412 model_->Revert(); 413 TextChanged(); 414 } 415 416 void 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 431 void AutocompleteEditViewViews::ClosePopup() { 432 model_->StopAutocomplete(); 433 } 434 435 void AutocompleteEditViewViews::SetFocus() { 436 // In views-implementation, the focus is on textfield rather than 437 // AutocompleteEditView. 438 textfield_->RequestFocus(); 439 } 440 441 void 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 451 bool 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 462 void AutocompleteEditViewViews::OnRevertTemporaryText() { 463 textfield_->SelectRange(saved_temporary_selection_); 464 TextChanged(); 465 } 466 467 void 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 474 bool 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 513 gfx::NativeView AutocompleteEditViewViews::GetNativeView() const { 514 return GetWidget()->GetNativeView(); 515 } 516 517 CommandUpdater* AutocompleteEditViewViews::GetCommandUpdater() { 518 return command_updater_; 519 } 520 521 void AutocompleteEditViewViews::SetInstantSuggestion(const string16& input, 522 bool animate_to_complete) { 523 NOTIMPLEMENTED(); 524 } 525 526 string16 AutocompleteEditViewViews::GetInstantSuggestion() const { 527 NOTIMPLEMENTED(); 528 return string16(); 529 } 530 531 int AutocompleteEditViewViews::TextWidth() const { 532 // TODO(oshima): add horizontal margin. 533 return textfield_->font().GetStringWidth(textfield_->text()); 534 } 535 536 bool AutocompleteEditViewViews::IsImeComposing() const { 537 return false; 538 } 539 540 views::View* AutocompleteEditViewViews::AddToView(views::View* parent) { 541 parent->AddChildView(this); 542 AddChildView(textfield_); 543 return this; 544 } 545 546 int AutocompleteEditViewViews::OnPerformDrop( 547 const views::DropTargetEvent& event) { 548 NOTIMPLEMENTED(); 549 return ui::DragDropTypes::DRAG_NONE; 550 } 551 552 //////////////////////////////////////////////////////////////////////////////// 553 // AutocompleteEditViewViews, NotificationObserver implementation: 554 555 void 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 565 void AutocompleteEditViewViews::ContentsChanged(views::Textfield* sender, 566 const string16& new_contents) { 567 } 568 569 bool 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 597 void AutocompleteEditViewViews::OnBeforeUserAction(views::Textfield* sender) { 598 OnBeforePossibleChange(); 599 } 600 601 void AutocompleteEditViewViews::OnAfterUserAction(views::Textfield* sender) { 602 OnAfterPossibleChange(); 603 } 604 605 //////////////////////////////////////////////////////////////////////////////// 606 // AutocompleteEditViewViews, private: 607 608 size_t AutocompleteEditViewViews::GetTextLength() const { 609 // TODO(oshima): Support instant, IME. 610 return textfield_->text().length(); 611 } 612 613 void AutocompleteEditViewViews::EmphasizeURLComponents() { 614 // TODO(oshima): Update URL visual style 615 NOTIMPLEMENTED(); 616 } 617 618 void AutocompleteEditViewViews::TextChanged() { 619 EmphasizeURLComponents(); 620 model_->OnChanged(); 621 } 622 623 void 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 631 string16 AutocompleteEditViewViews::GetSelectedText() const { 632 // TODO(oshima): Support instant, IME. 633 return textfield_->GetSelectedText(); 634 } 635 636 void AutocompleteEditViewViews::SelectRange(size_t caret, size_t end) { 637 const ui::Range range(caret, end); 638 textfield_->SelectRange(range); 639 } 640 641 AutocompletePopupView* 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