Add a minor text member to ui::MenuModel.
[chromium-blink-merge.git] / chrome / browser / ui / omnibox / omnibox_popup_model.cc
blob09102d1dde0a15715ad2de1730cb27a0829676fb
1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
7 #include <algorithm>
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/autocomplete/autocomplete_match.h"
12 #include "chrome/browser/extensions/api/omnibox/omnibox_api.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/search_engines/template_url.h"
15 #include "chrome/browser/search_engines/template_url_service.h"
16 #include "chrome/browser/search_engines/template_url_service_factory.h"
17 #include "chrome/browser/ui/omnibox/omnibox_popup_model_observer.h"
18 #include "chrome/browser/ui/omnibox/omnibox_popup_view.h"
19 #include "third_party/icu/source/common/unicode/ubidi.h"
20 #include "ui/gfx/image/image.h"
21 #include "ui/gfx/rect.h"
23 ///////////////////////////////////////////////////////////////////////////////
24 // OmniboxPopupModel
26 const size_t OmniboxPopupModel::kNoMatch = -1;
28 OmniboxPopupModel::OmniboxPopupModel(
29 OmniboxPopupView* popup_view,
30 OmniboxEditModel* edit_model)
31 : view_(popup_view),
32 edit_model_(edit_model),
33 hovered_line_(kNoMatch),
34 selected_line_(kNoMatch),
35 selected_line_state_(NORMAL) {
36 edit_model->set_popup_model(this);
39 OmniboxPopupModel::~OmniboxPopupModel() {
42 bool OmniboxPopupModel::IsOpen() const {
43 return view_->IsOpen();
46 void OmniboxPopupModel::SetHoveredLine(size_t line) {
47 const bool is_disabling = (line == kNoMatch);
48 DCHECK(is_disabling || (line < result().size()));
50 if (line == hovered_line_)
51 return; // Nothing to do
53 // Make sure the old hovered line is redrawn. No need to redraw the selected
54 // line since selection overrides hover so the appearance won't change.
55 if ((hovered_line_ != kNoMatch) && (hovered_line_ != selected_line_))
56 view_->InvalidateLine(hovered_line_);
58 // Change the hover to the new line.
59 hovered_line_ = line;
60 if (!is_disabling && (hovered_line_ != selected_line_))
61 view_->InvalidateLine(hovered_line_);
64 void OmniboxPopupModel::SetSelectedLine(size_t line,
65 bool reset_to_default,
66 bool force) {
67 const AutocompleteResult& result = this->result();
68 if (result.empty())
69 return;
71 // Cancel the query so the matches don't change on the user.
72 autocomplete_controller()->Stop(false);
74 line = std::min(line, result.size() - 1);
75 const AutocompleteMatch& match = result.match_at(line);
76 if (reset_to_default) {
77 manually_selected_match_.Clear();
78 } else {
79 // Track the user's selection until they cancel it.
80 manually_selected_match_.destination_url = match.destination_url;
81 manually_selected_match_.provider_affinity = match.provider;
82 manually_selected_match_.is_history_what_you_typed_match =
83 match.is_history_what_you_typed_match;
86 if (line == selected_line_ && !force)
87 return; // Nothing else to do.
89 // We need to update |selected_line_state_| and |selected_line_| before
90 // calling InvalidateLine(), since it will check them to determine how to
91 // draw. We also need to update |selected_line_| before calling
92 // OnPopupDataChanged(), so that when the edit notifies its controller that
93 // something has changed, the controller can get the correct updated data.
95 // NOTE: We should never reach here with no selected line; the same code that
96 // opened the popup and made it possible to get here should have also set a
97 // selected line.
98 CHECK(selected_line_ != kNoMatch);
99 GURL current_destination(result.match_at(selected_line_).destination_url);
100 const size_t prev_selected_line = selected_line_;
101 selected_line_state_ = NORMAL;
102 selected_line_ = line;
103 view_->InvalidateLine(prev_selected_line);
104 view_->InvalidateLine(selected_line_);
106 // Update the edit with the new data for this match.
107 // TODO(pkasting): If |selected_line_| moves to the controller, this can be
108 // eliminated and just become a call to the observer on the edit.
109 string16 keyword;
110 bool is_keyword_hint;
111 match.GetKeywordUIState(edit_model_->profile(), &keyword, &is_keyword_hint);
113 if (reset_to_default) {
114 edit_model_->OnPopupDataChanged(match.inline_autocompletion, NULL,
115 keyword, is_keyword_hint);
116 } else {
117 edit_model_->OnPopupDataChanged(match.fill_into_edit, &current_destination,
118 keyword, is_keyword_hint);
121 // Repaint old and new selected lines immediately, so that the edit doesn't
122 // appear to update [much] faster than the popup.
123 view_->PaintUpdatesNow();
126 void OmniboxPopupModel::ResetToDefaultMatch() {
127 const AutocompleteResult& result = this->result();
128 CHECK(!result.empty());
129 SetSelectedLine(result.default_match() - result.begin(), true, false);
130 view_->OnDragCanceled();
133 void OmniboxPopupModel::Move(int count) {
134 const AutocompleteResult& result = this->result();
135 if (result.empty())
136 return;
138 // The user is using the keyboard to change the selection, so stop tracking
139 // hover.
140 SetHoveredLine(kNoMatch);
142 // Clamp the new line to [0, result_.count() - 1].
143 const size_t new_line = selected_line_ + count;
144 SetSelectedLine(((count < 0) && (new_line >= selected_line_)) ? 0 : new_line,
145 false, false);
148 void OmniboxPopupModel::SetSelectedLineState(LineState state) {
149 DCHECK(!result().empty());
150 DCHECK_NE(kNoMatch, selected_line_);
152 const AutocompleteMatch& match = result().match_at(selected_line_);
153 DCHECK(match.associated_keyword.get());
155 selected_line_state_ = state;
156 view_->InvalidateLine(selected_line_);
159 void OmniboxPopupModel::TryDeletingCurrentItem() {
160 // We could use GetInfoForCurrentText() here, but it seems better to try
161 // and shift-delete the actual selection, rather than any "in progress, not
162 // yet visible" one.
163 if (selected_line_ == kNoMatch)
164 return;
166 // Cancel the query so the matches don't change on the user.
167 autocomplete_controller()->Stop(false);
169 const AutocompleteMatch& match = result().match_at(selected_line_);
170 if (match.deletable) {
171 const size_t selected_line = selected_line_;
172 const bool was_temporary_text = !manually_selected_match_.empty();
174 // This will synchronously notify both the edit and us that the results
175 // have changed, causing both to revert to the default match.
176 autocomplete_controller()->DeleteMatch(match);
177 const AutocompleteResult& result = this->result();
178 if (!result.empty() &&
179 (was_temporary_text || selected_line != selected_line_)) {
180 // Move the selection to the next choice after the deleted one.
181 // SetSelectedLine() will clamp to take care of the case where we deleted
182 // the last item.
183 // TODO(pkasting): Eventually the controller should take care of this
184 // before notifying us, reducing flicker. At that point the check for
185 // deletability can move there too.
186 SetSelectedLine(selected_line, false, true);
191 gfx::Image OmniboxPopupModel::GetIconIfExtensionMatch(
192 const AutocompleteMatch& match) const {
193 Profile* profile = edit_model_->profile();
194 const TemplateURL* template_url = match.GetTemplateURL(profile, false);
195 if (template_url && template_url->IsExtensionKeyword()) {
196 return extensions::OmniboxAPI::Get(profile)->GetOmniboxPopupIcon(
197 template_url->GetExtensionId());
199 return gfx::Image();
202 void OmniboxPopupModel::OnResultChanged() {
203 const AutocompleteResult& result = this->result();
204 selected_line_ = result.default_match() == result.end() ?
205 kNoMatch : static_cast<size_t>(result.default_match() - result.begin());
206 // There had better not be a nonempty result set with no default match.
207 CHECK((selected_line_ != kNoMatch) || result.empty());
208 manually_selected_match_.Clear();
209 selected_line_state_ = NORMAL;
210 // If we're going to trim the window size to no longer include the hovered
211 // line, turn hover off. Practically, this shouldn't happen, but it
212 // doesn't hurt to be defensive.
213 if ((hovered_line_ != kNoMatch) && (result.size() <= hovered_line_))
214 SetHoveredLine(kNoMatch);
216 bool popup_was_open = view_->IsOpen();
217 view_->UpdatePopupAppearance();
218 // If popup has just been shown or hidden, notify observers.
219 if (view_->IsOpen() != popup_was_open) {
220 FOR_EACH_OBSERVER(OmniboxPopupModelObserver, observers_,
221 OnOmniboxPopupShownOrHidden());
225 void OmniboxPopupModel::AddObserver(OmniboxPopupModelObserver* observer) {
226 observers_.AddObserver(observer);
229 void OmniboxPopupModel::RemoveObserver(OmniboxPopupModelObserver* observer) {
230 observers_.RemoveObserver(observer);