summaryrefslogtreecommitdiffstats
path: root/chrome/browser/autocomplete/autocomplete_popup_model.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/autocomplete/autocomplete_popup_model.cc')
-rw-r--r--chrome/browser/autocomplete/autocomplete_popup_model.cc291
1 files changed, 291 insertions, 0 deletions
diff --git a/chrome/browser/autocomplete/autocomplete_popup_model.cc b/chrome/browser/autocomplete/autocomplete_popup_model.cc
new file mode 100644
index 0000000..f4f7a82
--- /dev/null
+++ b/chrome/browser/autocomplete/autocomplete_popup_model.cc
@@ -0,0 +1,291 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autocomplete/autocomplete_popup_model.h"
+
+#include "base/string_util.h"
+#include "chrome/browser/autocomplete/autocomplete_edit.h"
+#include "chrome/browser/autocomplete/autocomplete_popup_view.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/search_engines/template_url_model.h"
+#include "chrome/common/notification_service.h"
+#include "third_party/icu/public/common/unicode/ubidi.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// AutocompletePopupModel
+
+AutocompletePopupModel::AutocompletePopupModel(
+ AutocompletePopupView* popup_view,
+ AutocompleteEditModel* edit_model,
+ Profile* profile)
+ : view_(popup_view),
+ edit_model_(edit_model),
+ controller_(new AutocompleteController(profile)),
+ profile_(profile),
+ hovered_line_(kNoMatch),
+ selected_line_(kNoMatch) {
+ registrar_.Add(this, NotificationType::AUTOCOMPLETE_CONTROLLER_RESULT_UPDATED,
+ Source<AutocompleteController>(controller_.get()));
+}
+
+AutocompletePopupModel::~AutocompletePopupModel() {
+}
+
+void AutocompletePopupModel::SetProfile(Profile* profile) {
+ DCHECK(profile);
+ profile_ = profile;
+ controller_->SetProfile(profile);
+}
+
+void AutocompletePopupModel::StartAutocomplete(
+ const std::wstring& text,
+ const std::wstring& desired_tld,
+ bool prevent_inline_autocomplete,
+ bool prefer_keyword) {
+ // The user is interacting with the edit, so stop tracking hover.
+ SetHoveredLine(kNoMatch);
+
+ manually_selected_match_.Clear();
+
+ controller_->Start(text, desired_tld, prevent_inline_autocomplete,
+ prefer_keyword, false);
+}
+
+void AutocompletePopupModel::StopAutocomplete() {
+ controller_->Stop(true);
+}
+
+bool AutocompletePopupModel::IsOpen() const {
+ return view_->IsOpen();
+}
+
+void AutocompletePopupModel::SetHoveredLine(size_t line) {
+ const bool is_disabling = (line == kNoMatch);
+ DCHECK(is_disabling || (line < controller_->result().size()));
+
+ if (line == hovered_line_)
+ return; // Nothing to do
+
+ // Make sure the old hovered line is redrawn. No need to redraw the selected
+ // line since selection overrides hover so the appearance won't change.
+ if ((hovered_line_ != kNoMatch) && (hovered_line_ != selected_line_))
+ view_->InvalidateLine(hovered_line_);
+
+ // Change the hover to the new line.
+ hovered_line_ = line;
+ if (!is_disabling && (hovered_line_ != selected_line_))
+ view_->InvalidateLine(hovered_line_);
+}
+
+void AutocompletePopupModel::SetSelectedLine(size_t line,
+ bool reset_to_default) {
+ const AutocompleteResult& result = controller_->result();
+ CHECK(line < result.size());
+ if (result.empty())
+ return;
+
+ // Cancel the query so the matches don't change on the user.
+ controller_->Stop(false);
+
+ const AutocompleteMatch& match = result.match_at(line);
+ if (reset_to_default) {
+ manually_selected_match_.Clear();
+ } else {
+ // Track the user's selection until they cancel it.
+ manually_selected_match_.destination_url = match.destination_url;
+ manually_selected_match_.provider_affinity = match.provider;
+ manually_selected_match_.is_history_what_you_typed_match =
+ match.is_history_what_you_typed_match;
+ }
+
+ if (line == selected_line_)
+ return; // Nothing else to do.
+
+ // We need to update |selected_line_| before calling OnPopupDataChanged(), so
+ // that when the edit notifies its controller that something has changed, the
+ // controller can get the correct updated data.
+ //
+ // NOTE: We should never reach here with no selected line; the same code that
+ // opened the popup and made it possible to get here should have also set a
+ // selected line.
+ CHECK(selected_line_ != kNoMatch);
+ GURL current_destination(result.match_at(selected_line_).destination_url);
+ view_->InvalidateLine(selected_line_);
+ selected_line_ = line;
+ view_->InvalidateLine(selected_line_);
+
+ // Update the edit with the new data for this match.
+ // TODO(pkasting): If |selected_line_| moves to the controller, this can be
+ // eliminated and just become a call to the observer on the edit.
+ std::wstring keyword;
+ const bool is_keyword_hint = GetKeywordForMatch(match, &keyword);
+ if (reset_to_default) {
+ std::wstring inline_autocomplete_text;
+ if ((match.inline_autocomplete_offset != std::wstring::npos) &&
+ (match.inline_autocomplete_offset < match.fill_into_edit.length())) {
+ inline_autocomplete_text =
+ match.fill_into_edit.substr(match.inline_autocomplete_offset);
+ }
+ edit_model_->OnPopupDataChanged(inline_autocomplete_text, NULL,
+ keyword, is_keyword_hint);
+ } else {
+ edit_model_->OnPopupDataChanged(match.fill_into_edit, &current_destination,
+ keyword, is_keyword_hint);
+ }
+
+ // Repaint old and new selected lines immediately, so that the edit doesn't
+ // appear to update [much] faster than the popup.
+ view_->PaintUpdatesNow();
+}
+
+void AutocompletePopupModel::ResetToDefaultMatch() {
+ const AutocompleteResult& result = controller_->result();
+ CHECK(!result.empty());
+ SetSelectedLine(result.default_match() - result.begin(), true);
+ view_->OnDragCanceled();
+}
+
+void AutocompletePopupModel::InfoForCurrentSelection(
+ AutocompleteMatch* match,
+ GURL* alternate_nav_url) const {
+ DCHECK(match != NULL);
+ const AutocompleteResult* result;
+ if (!controller_->done()) {
+ result = &controller_->latest_result();
+ // It's technically possible for |result| to be empty if no provider returns
+ // a synchronous result but the query has not completed synchronously;
+ // pratically, however, that should never actually happen.
+ if (result->empty())
+ return;
+ // The user cannot have manually selected a match, or the query would have
+ // stopped. So the default match must be the desired selection.
+ *match = *result->default_match();
+ } else {
+ CHECK(IsOpen());
+ // The query isn't running, so the standard result set can't possibly be out
+ // of date.
+ //
+ // NOTE: In practice, it should actually be safe to use
+ // controller_->latest_result() here too, since the controller keeps that
+ // up-to-date. However we generally try to avoid referring to that.
+ result = &controller_->result();
+ // If there are no results, the popup should be closed (so we should have
+ // failed the CHECK above), and URLsForDefaultMatch() should have been
+ // called instead.
+ CHECK(!result->empty());
+ CHECK(selected_line_ < result->size());
+ *match = result->match_at(selected_line_);
+ }
+ if (alternate_nav_url && manually_selected_match_.empty())
+ *alternate_nav_url = result->alternate_nav_url();
+}
+
+bool AutocompletePopupModel::GetKeywordForMatch(const AutocompleteMatch& match,
+ std::wstring* keyword) const {
+ // Assume we have no keyword until we find otherwise.
+ keyword->clear();
+
+ // If the current match is a keyword, return that as the selected keyword.
+ if (TemplateURL::SupportsReplacement(match.template_url)) {
+ keyword->assign(match.template_url->keyword());
+ return false;
+ }
+
+ // See if the current match's fill_into_edit corresponds to a keyword.
+ if (!profile_->GetTemplateURLModel())
+ return false;
+ profile_->GetTemplateURLModel()->Load();
+ const std::wstring keyword_hint(
+ TemplateURLModel::CleanUserInputKeyword(match.fill_into_edit));
+ if (keyword_hint.empty())
+ return false;
+
+ // Don't provide a hint if this keyword doesn't support replacement.
+ const TemplateURL* const template_url =
+ profile_->GetTemplateURLModel()->GetTemplateURLForKeyword(keyword_hint);
+ if (!TemplateURL::SupportsReplacement(template_url))
+ return false;
+
+ keyword->assign(keyword_hint);
+ return true;
+}
+
+AutocompleteLog* AutocompletePopupModel::GetAutocompleteLog() {
+ return new AutocompleteLog(controller_->input().text(),
+ controller_->input().type(), selected_line_, 0, controller_->result());
+}
+
+void AutocompletePopupModel::Move(int count) {
+ const AutocompleteResult& result = controller_->result();
+ if (result.empty())
+ return;
+
+ // The user is using the keyboard to change the selection, so stop tracking
+ // hover.
+ SetHoveredLine(kNoMatch);
+
+ // Clamp the new line to [0, result_.count() - 1].
+ const size_t new_line = selected_line_ + count;
+ SetSelectedLine((((count < 0) && (new_line >= selected_line_)) ?
+ 0 : std::min(new_line, result.size() - 1)), false);
+}
+
+void AutocompletePopupModel::TryDeletingCurrentItem() {
+ // We could use InfoForCurrentSelection() here, but it seems better to try
+ // and shift-delete the actual selection, rather than any "in progress, not
+ // yet visible" one.
+ if (selected_line_ == kNoMatch)
+ return;
+
+ // Cancel the query so the matches don't change on the user.
+ controller_->Stop(false);
+
+ const AutocompleteMatch& match =
+ controller_->result().match_at(selected_line_);
+ if (match.deletable) {
+ const size_t selected_line = selected_line_;
+ controller_->DeleteMatch(match); // This may synchronously notify us that
+ // the results have changed.
+ const AutocompleteResult& result = controller_->result();
+ if (!result.empty()) {
+ // Move the selection to the next choice after the deleted one.
+ // TODO(pkasting): Eventually the controller should take care of this
+ // before notifying us, reducing flicker. At that point the check for
+ // deletability can move there too.
+ SetSelectedLine(std::min(result.size() - 1, selected_line), false);
+ }
+ }
+}
+
+void AutocompletePopupModel::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK_EQ(NotificationType::AUTOCOMPLETE_CONTROLLER_RESULT_UPDATED,
+ type.value);
+
+ const AutocompleteResult* result =
+ Details<const AutocompleteResult>(details).ptr();
+ selected_line_ = (result->default_match() == result->end()) ?
+ kNoMatch : (result->default_match() - result->begin());
+ // There had better not be a nonempty result set with no default match.
+ CHECK((selected_line_ != kNoMatch) || result->empty());
+ // If we're going to trim the window size to no longer include the hovered
+ // line, turn hover off. Practically, this shouldn't happen, but it
+ // doesn't hurt to be defensive.
+ if ((hovered_line_ != kNoMatch) && (result->size() <= hovered_line_))
+ SetHoveredLine(kNoMatch);
+
+ view_->UpdatePopupAppearance();
+}
+
+const SkBitmap* AutocompletePopupModel::GetSpecialIconForMatch(
+ const AutocompleteMatch& match) const {
+ if (!match.template_url || !match.template_url->IsExtensionKeyword())
+ return NULL;
+
+ return &profile_->GetExtensionsService()->GetOmniboxIcon(
+ match.template_url->GetExtensionId());
+}