OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/autocomplete/autocomplete_popup_model.h" | 5 #include "chrome/browser/autocomplete/autocomplete_popup_model.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "unicode/ubidi.h" | 9 #include "unicode/ubidi.h" |
10 | 10 |
(...skipping 13 matching lines...) Expand all Loading... |
24 // AutocompletePopupModel | 24 // AutocompletePopupModel |
25 | 25 |
26 AutocompletePopupModel::AutocompletePopupModel( | 26 AutocompletePopupModel::AutocompletePopupModel( |
27 AutocompletePopupView* popup_view, | 27 AutocompletePopupView* popup_view, |
28 AutocompleteEditModel* edit_model, | 28 AutocompleteEditModel* edit_model, |
29 Profile* profile) | 29 Profile* profile) |
30 : view_(popup_view), | 30 : view_(popup_view), |
31 edit_model_(edit_model), | 31 edit_model_(edit_model), |
32 profile_(profile), | 32 profile_(profile), |
33 hovered_line_(kNoMatch), | 33 hovered_line_(kNoMatch), |
34 selected_line_(kNoMatch) { | 34 selected_line_(kNoMatch), |
| 35 match_state_(NORMAL) { |
35 edit_model->set_popup_model(this); | 36 edit_model->set_popup_model(this); |
36 } | 37 } |
37 | 38 |
38 AutocompletePopupModel::~AutocompletePopupModel() { | 39 AutocompletePopupModel::~AutocompletePopupModel() { |
39 } | 40 } |
40 | 41 |
41 bool AutocompletePopupModel::IsOpen() const { | 42 bool AutocompletePopupModel::IsOpen() const { |
42 return view_->IsOpen(); | 43 return view_->IsOpen(); |
43 } | 44 } |
44 | 45 |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
87 | 88 |
88 // We need to update |selected_line_| before calling OnPopupDataChanged(), so | 89 // We need to update |selected_line_| before calling OnPopupDataChanged(), so |
89 // that when the edit notifies its controller that something has changed, the | 90 // that when the edit notifies its controller that something has changed, the |
90 // controller can get the correct updated data. | 91 // controller can get the correct updated data. |
91 // | 92 // |
92 // NOTE: We should never reach here with no selected line; the same code that | 93 // NOTE: We should never reach here with no selected line; the same code that |
93 // opened the popup and made it possible to get here should have also set a | 94 // opened the popup and made it possible to get here should have also set a |
94 // selected line. | 95 // selected line. |
95 CHECK(selected_line_ != kNoMatch); | 96 CHECK(selected_line_ != kNoMatch); |
96 GURL current_destination(result.match_at(selected_line_).destination_url); | 97 GURL current_destination(result.match_at(selected_line_).destination_url); |
| 98 match_state_ = NORMAL; |
97 view_->InvalidateLine(selected_line_); | 99 view_->InvalidateLine(selected_line_); |
98 selected_line_ = line; | 100 selected_line_ = line; |
99 view_->InvalidateLine(selected_line_); | 101 view_->InvalidateLine(selected_line_); |
100 | 102 |
101 // Update the edit with the new data for this match. | 103 // Update the edit with the new data for this match. |
102 // TODO(pkasting): If |selected_line_| moves to the controller, this can be | 104 // TODO(pkasting): If |selected_line_| moves to the controller, this can be |
103 // eliminated and just become a call to the observer on the edit. | 105 // eliminated and just become a call to the observer on the edit. |
104 string16 keyword; | 106 string16 keyword = match.keyword; |
105 const bool is_keyword_hint = GetKeywordForMatch(match, &keyword); | 107 const bool is_keyword_hint = match.associated_keyword.get() != NULL; |
| 108 if (is_keyword_hint) |
| 109 keyword = match.associated_keyword->keyword; |
| 110 |
106 if (reset_to_default) { | 111 if (reset_to_default) { |
107 string16 inline_autocomplete_text; | 112 string16 inline_autocomplete_text; |
108 if ((match.inline_autocomplete_offset != string16::npos) && | 113 if ((match.inline_autocomplete_offset != string16::npos) && |
109 (match.inline_autocomplete_offset < match.fill_into_edit.length())) { | 114 (match.inline_autocomplete_offset < match.fill_into_edit.length())) { |
110 inline_autocomplete_text = | 115 inline_autocomplete_text = |
111 match.fill_into_edit.substr(match.inline_autocomplete_offset); | 116 match.fill_into_edit.substr(match.inline_autocomplete_offset); |
112 } | 117 } |
113 edit_model_->OnPopupDataChanged(inline_autocomplete_text, NULL, | 118 edit_model_->OnPopupDataChanged(inline_autocomplete_text, NULL, |
114 keyword, is_keyword_hint); | 119 keyword, is_keyword_hint); |
115 } else { | 120 } else { |
116 edit_model_->OnPopupDataChanged(match.fill_into_edit, ¤t_destination, | 121 edit_model_->OnPopupDataChanged(match.fill_into_edit, ¤t_destination, |
117 keyword, is_keyword_hint); | 122 keyword, is_keyword_hint); |
118 } | 123 } |
119 | 124 |
120 // Repaint old and new selected lines immediately, so that the edit doesn't | 125 // Repaint old and new selected lines immediately, so that the edit doesn't |
121 // appear to update [much] faster than the popup. | 126 // appear to update [much] faster than the popup. |
122 view_->PaintUpdatesNow(); | 127 view_->PaintUpdatesNow(); |
123 } | 128 } |
124 | 129 |
125 void AutocompletePopupModel::ResetToDefaultMatch() { | 130 void AutocompletePopupModel::ResetToDefaultMatch() { |
126 const AutocompleteResult& result = this->result(); | 131 const AutocompleteResult& result = this->result(); |
127 CHECK(!result.empty()); | 132 CHECK(!result.empty()); |
128 SetSelectedLine(result.default_match() - result.begin(), true, false); | 133 SetSelectedLine(result.default_match() - result.begin(), true, false); |
129 view_->OnDragCanceled(); | 134 view_->OnDragCanceled(); |
130 } | 135 } |
131 | 136 |
132 bool AutocompletePopupModel::GetKeywordForMatch(const AutocompleteMatch& match, | |
133 string16* keyword) const { | |
134 // Assume we have no keyword until we find otherwise. | |
135 keyword->clear(); | |
136 | |
137 if (match.template_url) { | |
138 TemplateURLService* url_service = | |
139 TemplateURLServiceFactory::GetForProfile(profile_); | |
140 if (!url_service) | |
141 return false; | |
142 | |
143 // Only show the keyword for the default provider if the user typed in | |
144 // the keyword and it isn't SEARCH_WHAT_YOU_TYPED. | |
145 const TemplateURL* default_url = url_service->GetDefaultSearchProvider(); | |
146 if (default_url && (default_url->id() == match.template_url->id())) { | |
147 if (StartsWith(autocomplete_controller()->input().text(), | |
148 default_url->keyword(), false) && | |
149 (match.type != AutocompleteMatch::SEARCH_WHAT_YOU_TYPED)) { | |
150 keyword->assign(match.template_url->keyword()); | |
151 return false; | |
152 } | |
153 } else if (TemplateURL::SupportsReplacement(match.template_url)) { | |
154 // The current match is a keyword, return that as the selected keyword. | |
155 keyword->assign(match.template_url->keyword()); | |
156 return false; | |
157 } | |
158 } | |
159 | |
160 // See if the current match's fill_into_edit corresponds to a keyword. | |
161 return GetKeywordForText(match.fill_into_edit, keyword); | |
162 } | |
163 | |
164 bool AutocompletePopupModel::GetKeywordForText(const string16& text, | |
165 string16* keyword) const { | |
166 // Creates keyword_hint first in case |keyword| is a pointer to |text|. | |
167 const string16 keyword_hint(TemplateURLService::CleanUserInputKeyword(text)); | |
168 | |
169 // Assume we have no keyword until we find otherwise. | |
170 keyword->clear(); | |
171 | |
172 if (keyword_hint.empty()) | |
173 return false; | |
174 TemplateURLService* url_service = | |
175 TemplateURLServiceFactory::GetForProfile(profile_); | |
176 if (!url_service) | |
177 return false; | |
178 url_service->Load(); | |
179 | |
180 // Don't provide a hint if this keyword doesn't support replacement. | |
181 const TemplateURL* const template_url = | |
182 url_service->GetTemplateURLForKeyword(keyword_hint); | |
183 if (!TemplateURL::SupportsReplacement(template_url)) | |
184 return false; | |
185 | |
186 // Don't provide a hint for inactive/disabled extension keywords. | |
187 if (template_url->IsExtensionKeyword()) { | |
188 const Extension* extension = profile_->GetExtensionService()-> | |
189 GetExtensionById(template_url->GetExtensionId(), false); | |
190 if (!extension || | |
191 (profile_->IsOffTheRecord() && | |
192 !profile_->GetExtensionService()-> | |
193 IsIncognitoEnabled(extension->id()))) | |
194 return false; | |
195 } | |
196 | |
197 keyword->assign(keyword_hint); | |
198 return true; | |
199 } | |
200 | |
201 void AutocompletePopupModel::Move(int count) { | 137 void AutocompletePopupModel::Move(int count) { |
202 const AutocompleteResult& result = this->result(); | 138 const AutocompleteResult& result = this->result(); |
203 if (result.empty()) | 139 if (result.empty()) |
204 return; | 140 return; |
205 | 141 |
206 // The user is using the keyboard to change the selection, so stop tracking | 142 // The user is using the keyboard to change the selection, so stop tracking |
207 // hover. | 143 // hover. |
208 SetHoveredLine(kNoMatch); | 144 SetHoveredLine(kNoMatch); |
209 | 145 |
210 // Clamp the new line to [0, result_.count() - 1]. | 146 // Clamp the new line to [0, result_.count() - 1]. |
211 const size_t new_line = selected_line_ + count; | 147 const size_t new_line = selected_line_ + count; |
212 SetSelectedLine(((count < 0) && (new_line >= selected_line_)) ? 0 : new_line, | 148 SetSelectedLine(((count < 0) && (new_line >= selected_line_)) ? 0 : new_line, |
213 false, false); | 149 false, false); |
214 } | 150 } |
215 | 151 |
| 152 void AutocompletePopupModel::SetSelectedMatch(MatchState state) { |
| 153 DCHECK(!result().empty()); |
| 154 DCHECK_NE(selected_line_, kNoMatch); |
| 155 |
| 156 const AutocompleteMatch& match = result().match_at(selected_line_); |
| 157 DCHECK(match.associated_keyword.get()); |
| 158 |
| 159 match_state_ = state; |
| 160 view_->InvalidateLine(selected_line_); |
| 161 } |
| 162 |
216 void AutocompletePopupModel::TryDeletingCurrentItem() { | 163 void AutocompletePopupModel::TryDeletingCurrentItem() { |
217 // We could use InfoForCurrentSelection() here, but it seems better to try | 164 // We could use InfoForCurrentSelection() here, but it seems better to try |
218 // and shift-delete the actual selection, rather than any "in progress, not | 165 // and shift-delete the actual selection, rather than any "in progress, not |
219 // yet visible" one. | 166 // yet visible" one. |
220 if (selected_line_ == kNoMatch) | 167 if (selected_line_ == kNoMatch) |
221 return; | 168 return; |
222 | 169 |
223 // Cancel the query so the matches don't change on the user. | 170 // Cancel the query so the matches don't change on the user. |
224 autocomplete_controller()->Stop(false); | 171 autocomplete_controller()->Stop(false); |
225 | 172 |
(...skipping 28 matching lines...) Expand all Loading... |
254 match.template_url->GetExtensionId()); | 201 match.template_url->GetExtensionId()); |
255 } | 202 } |
256 | 203 |
257 void AutocompletePopupModel::OnResultChanged() { | 204 void AutocompletePopupModel::OnResultChanged() { |
258 const AutocompleteResult& result = this->result(); | 205 const AutocompleteResult& result = this->result(); |
259 selected_line_ = result.default_match() == result.end() ? | 206 selected_line_ = result.default_match() == result.end() ? |
260 kNoMatch : static_cast<size_t>(result.default_match() - result.begin()); | 207 kNoMatch : static_cast<size_t>(result.default_match() - result.begin()); |
261 // There had better not be a nonempty result set with no default match. | 208 // There had better not be a nonempty result set with no default match. |
262 CHECK((selected_line_ != kNoMatch) || result.empty()); | 209 CHECK((selected_line_ != kNoMatch) || result.empty()); |
263 manually_selected_match_.Clear(); | 210 manually_selected_match_.Clear(); |
| 211 match_state_ = NORMAL; |
264 // If we're going to trim the window size to no longer include the hovered | 212 // If we're going to trim the window size to no longer include the hovered |
265 // line, turn hover off. Practically, this shouldn't happen, but it | 213 // line, turn hover off. Practically, this shouldn't happen, but it |
266 // doesn't hurt to be defensive. | 214 // doesn't hurt to be defensive. |
267 if ((hovered_line_ != kNoMatch) && (result.size() <= hovered_line_)) | 215 if ((hovered_line_ != kNoMatch) && (result.size() <= hovered_line_)) |
268 SetHoveredLine(kNoMatch); | 216 SetHoveredLine(kNoMatch); |
269 | 217 |
270 view_->UpdatePopupAppearance(); | 218 view_->UpdatePopupAppearance(); |
271 } | 219 } |
OLD | NEW |