summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/omnibox/omnibox_popup_model.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/omnibox/omnibox_popup_model.cc')
-rw-r--r--chrome/browser/ui/omnibox/omnibox_popup_model.cc221
1 files changed, 221 insertions, 0 deletions
diff --git a/chrome/browser/ui/omnibox/omnibox_popup_model.cc b/chrome/browser/ui/omnibox/omnibox_popup_model.cc
new file mode 100644
index 0000000..f4b5bdd
--- /dev/null
+++ b/chrome/browser/ui/omnibox/omnibox_popup_model.cc
@@ -0,0 +1,221 @@
+// Copyright (c) 2012 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/ui/omnibox/omnibox_popup_model.h"
+
+#include <algorithm>
+
+#include "unicode/ubidi.h"
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/autocomplete/autocomplete_edit.h"
+#include "chrome/browser/autocomplete/autocomplete_match.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/search_engines/template_url_service.h"
+#include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "chrome/browser/ui/omnibox/omnibox_popup_view.h"
+#include "ui/gfx/rect.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// OmniboxPopupModel
+
+const size_t OmniboxPopupModel::kNoMatch = -1;
+
+OmniboxPopupModel::OmniboxPopupModel(
+ OmniboxPopupView* popup_view,
+ AutocompleteEditModel* edit_model)
+ : view_(popup_view),
+ edit_model_(edit_model),
+ hovered_line_(kNoMatch),
+ selected_line_(kNoMatch),
+ selected_line_state_(NORMAL) {
+ edit_model->set_popup_model(this);
+}
+
+OmniboxPopupModel::~OmniboxPopupModel() {
+}
+
+bool OmniboxPopupModel::IsOpen() const {
+ return view_->IsOpen();
+}
+
+void OmniboxPopupModel::SetHoveredLine(size_t line) {
+ const bool is_disabling = (line == kNoMatch);
+ DCHECK(is_disabling || (line < 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 OmniboxPopupModel::SetSelectedLine(size_t line,
+ bool reset_to_default,
+ bool force) {
+ const AutocompleteResult& result = this->result();
+ if (result.empty())
+ return;
+
+ // Cancel the query so the matches don't change on the user.
+ autocomplete_controller()->Stop(false);
+
+ line = std::min(line, result.size() - 1);
+ 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_ && !force)
+ return; // Nothing else to do.
+
+ // We need to update |selected_line_state_| and |selected_line_| before
+ // calling InvalidateLine(), since it will check them to determine how to
+ // draw. We also 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);
+ const size_t prev_selected_line = selected_line_;
+ selected_line_state_ = NORMAL;
+ selected_line_ = line;
+ view_->InvalidateLine(prev_selected_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.
+ string16 keyword;
+ bool is_keyword_hint;
+ match.GetKeywordUIState(edit_model_->profile(), &keyword, &is_keyword_hint);
+
+ if (reset_to_default) {
+ string16 inline_autocomplete_text;
+ if ((match.inline_autocomplete_offset != string16::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 OmniboxPopupModel::ResetToDefaultMatch() {
+ const AutocompleteResult& result = this->result();
+ CHECK(!result.empty());
+ SetSelectedLine(result.default_match() - result.begin(), true, false);
+ view_->OnDragCanceled();
+}
+
+void OmniboxPopupModel::Move(int count) {
+ const AutocompleteResult& result = this->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 : new_line,
+ false, false);
+}
+
+void OmniboxPopupModel::SetSelectedLineState(LineState state) {
+ DCHECK(!result().empty());
+ DCHECK_NE(kNoMatch, selected_line_);
+
+ const AutocompleteMatch& match = result().match_at(selected_line_);
+ DCHECK(match.associated_keyword.get());
+
+ selected_line_state_ = state;
+ view_->InvalidateLine(selected_line_);
+}
+
+void OmniboxPopupModel::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.
+ autocomplete_controller()->Stop(false);
+
+ const AutocompleteMatch& match = result().match_at(selected_line_);
+ if (match.deletable) {
+ const size_t selected_line = selected_line_;
+ const bool was_temporary_text = !manually_selected_match_.empty();
+
+ // This will synchronously notify both the edit and us that the results
+ // have changed, causing both to revert to the default match.
+ autocomplete_controller()->DeleteMatch(match);
+ const AutocompleteResult& result = this->result();
+ if (!result.empty() &&
+ (was_temporary_text || selected_line != selected_line_)) {
+ // Move the selection to the next choice after the deleted one.
+ // SetSelectedLine() will clamp to take care of the case where we deleted
+ // the last item.
+ // 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(selected_line, false, true);
+ }
+ }
+}
+
+const SkBitmap* OmniboxPopupModel::GetIconIfExtensionMatch(
+ const AutocompleteMatch& match) const {
+ Profile* profile = edit_model_->profile();
+ const TemplateURL* template_url = match.GetTemplateURL(profile);
+ return (template_url && template_url->IsExtensionKeyword()) ?
+ &profile->GetExtensionService()->GetOmniboxPopupIcon(
+ template_url->GetExtensionId()) : NULL;
+}
+
+void OmniboxPopupModel::OnResultChanged() {
+ const AutocompleteResult& result = this->result();
+ selected_line_ = result.default_match() == result.end() ?
+ kNoMatch : static_cast<size_t>(result.default_match() - result.begin());
+ // There had better not be a nonempty result set with no default match.
+ CHECK((selected_line_ != kNoMatch) || result.empty());
+ manually_selected_match_.Clear();
+ selected_line_state_ = NORMAL;
+ // 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();
+}