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 |
11 #include "base/string_util.h" | 11 #include "base/string_util.h" |
12 #include "base/utf_string_conversions.h" | 12 #include "base/utf_string_conversions.h" |
13 #include "chrome/browser/autocomplete/autocomplete_edit.h" | 13 #include "chrome/browser/autocomplete/autocomplete_edit.h" |
14 #include "chrome/browser/autocomplete/autocomplete_match.h" | 14 #include "chrome/browser/autocomplete/autocomplete_match.h" |
15 #include "chrome/browser/autocomplete/autocomplete_popup_view.h" | 15 #include "chrome/browser/autocomplete/autocomplete_popup_view.h" |
16 #include "chrome/browser/extensions/extension_service.h" | 16 #include "chrome/browser/extensions/extension_service.h" |
17 #include "chrome/browser/profiles/profile.h" | 17 #include "chrome/browser/profiles/profile.h" |
18 #include "chrome/browser/search_engines/template_url.h" | 18 #include "chrome/browser/search_engines/template_url.h" |
19 #include "chrome/browser/search_engines/template_url_service.h" | 19 #include "chrome/browser/search_engines/template_url_service.h" |
20 #include "chrome/browser/search_engines/template_url_service_factory.h" | 20 #include "chrome/browser/search_engines/template_url_service_factory.h" |
21 #include "ui/gfx/rect.h" | 21 #include "ui/gfx/rect.h" |
22 | 22 |
23 /////////////////////////////////////////////////////////////////////////////// | 23 /////////////////////////////////////////////////////////////////////////////// |
24 // AutocompletePopupModel | 24 // AutocompletePopupModel |
25 | 25 |
26 const size_t AutocompletePopupModel::kNoMatch = -1; | |
27 | |
28 AutocompletePopupModel::AutocompletePopupModel( | 26 AutocompletePopupModel::AutocompletePopupModel( |
29 AutocompletePopupView* popup_view, | 27 AutocompletePopupView* popup_view, |
30 AutocompleteEditModel* edit_model) | 28 AutocompleteEditModel* edit_model) |
31 : view_(popup_view), | 29 : view_(popup_view), |
32 edit_model_(edit_model), | 30 edit_model_(edit_model), |
33 hovered_line_(kNoMatch), | 31 hovered_line_(kNoMatch), |
34 selected_line_(kNoMatch), | 32 selected_line_(kNoMatch) { |
35 selected_line_state_(NORMAL) { | |
36 edit_model->set_popup_model(this); | 33 edit_model->set_popup_model(this); |
37 } | 34 } |
38 | 35 |
39 AutocompletePopupModel::~AutocompletePopupModel() { | 36 AutocompletePopupModel::~AutocompletePopupModel() { |
40 } | 37 } |
41 | 38 |
42 bool AutocompletePopupModel::IsOpen() const { | 39 bool AutocompletePopupModel::IsOpen() const { |
43 return view_->IsOpen(); | 40 return view_->IsOpen(); |
44 } | 41 } |
45 | 42 |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
79 // Track the user's selection until they cancel it. | 76 // Track the user's selection until they cancel it. |
80 manually_selected_match_.destination_url = match.destination_url; | 77 manually_selected_match_.destination_url = match.destination_url; |
81 manually_selected_match_.provider_affinity = match.provider; | 78 manually_selected_match_.provider_affinity = match.provider; |
82 manually_selected_match_.is_history_what_you_typed_match = | 79 manually_selected_match_.is_history_what_you_typed_match = |
83 match.is_history_what_you_typed_match; | 80 match.is_history_what_you_typed_match; |
84 } | 81 } |
85 | 82 |
86 if (line == selected_line_ && !force) | 83 if (line == selected_line_ && !force) |
87 return; // Nothing else to do. | 84 return; // Nothing else to do. |
88 | 85 |
89 // We need to update |selected_line_state_| and |selected_line_| before | 86 // We need to update |selected_line_| before calling OnPopupDataChanged(), so |
90 // calling InvalidateLine(), since it will check them to determine how to | 87 // that when the edit notifies its controller that something has changed, the |
91 // draw. We also need to update |selected_line_| before calling | 88 // controller can get the correct updated data. |
92 // OnPopupDataChanged(), so that when the edit notifies its controller that | |
93 // something has changed, the controller can get the correct updated data. | |
94 // | 89 // |
95 // NOTE: We should never reach here with no selected line; the same code that | 90 // 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 | 91 // opened the popup and made it possible to get here should have also set a |
97 // selected line. | 92 // selected line. |
98 CHECK(selected_line_ != kNoMatch); | 93 CHECK(selected_line_ != kNoMatch); |
99 GURL current_destination(result.match_at(selected_line_).destination_url); | 94 GURL current_destination(result.match_at(selected_line_).destination_url); |
100 const size_t prev_selected_line = selected_line_; | 95 view_->InvalidateLine(selected_line_); |
101 selected_line_state_ = NORMAL; | |
102 selected_line_ = line; | 96 selected_line_ = line; |
103 view_->InvalidateLine(prev_selected_line); | |
104 view_->InvalidateLine(selected_line_); | 97 view_->InvalidateLine(selected_line_); |
105 | 98 |
106 // Update the edit with the new data for this match. | 99 // Update the edit with the new data for this match. |
107 // TODO(pkasting): If |selected_line_| moves to the controller, this can be | 100 // 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. | 101 // eliminated and just become a call to the observer on the edit. |
109 string16 keyword; | 102 string16 keyword; |
110 const bool is_keyword_hint = match.GetKeyword(&keyword); | 103 const bool is_keyword_hint = GetKeywordForMatch(match, &keyword); |
111 | |
112 if (reset_to_default) { | 104 if (reset_to_default) { |
113 string16 inline_autocomplete_text; | 105 string16 inline_autocomplete_text; |
114 if ((match.inline_autocomplete_offset != string16::npos) && | 106 if ((match.inline_autocomplete_offset != string16::npos) && |
115 (match.inline_autocomplete_offset < match.fill_into_edit.length())) { | 107 (match.inline_autocomplete_offset < match.fill_into_edit.length())) { |
116 inline_autocomplete_text = | 108 inline_autocomplete_text = |
117 match.fill_into_edit.substr(match.inline_autocomplete_offset); | 109 match.fill_into_edit.substr(match.inline_autocomplete_offset); |
118 } | 110 } |
119 edit_model_->OnPopupDataChanged(inline_autocomplete_text, NULL, | 111 edit_model_->OnPopupDataChanged(inline_autocomplete_text, NULL, |
120 keyword, is_keyword_hint); | 112 keyword, is_keyword_hint); |
121 } else { | 113 } else { |
122 edit_model_->OnPopupDataChanged(match.fill_into_edit, ¤t_destination, | 114 edit_model_->OnPopupDataChanged(match.fill_into_edit, ¤t_destination, |
123 keyword, is_keyword_hint); | 115 keyword, is_keyword_hint); |
124 } | 116 } |
125 | 117 |
126 // Repaint old and new selected lines immediately, so that the edit doesn't | 118 // Repaint old and new selected lines immediately, so that the edit doesn't |
127 // appear to update [much] faster than the popup. | 119 // appear to update [much] faster than the popup. |
128 view_->PaintUpdatesNow(); | 120 view_->PaintUpdatesNow(); |
129 } | 121 } |
130 | 122 |
131 void AutocompletePopupModel::ResetToDefaultMatch() { | 123 void AutocompletePopupModel::ResetToDefaultMatch() { |
132 const AutocompleteResult& result = this->result(); | 124 const AutocompleteResult& result = this->result(); |
133 CHECK(!result.empty()); | 125 CHECK(!result.empty()); |
134 SetSelectedLine(result.default_match() - result.begin(), true, false); | 126 SetSelectedLine(result.default_match() - result.begin(), true, false); |
135 view_->OnDragCanceled(); | 127 view_->OnDragCanceled(); |
136 } | 128 } |
137 | 129 |
| 130 bool AutocompletePopupModel::GetKeywordForMatch(const AutocompleteMatch& match, |
| 131 string16* keyword) const { |
| 132 // Assume we have no keyword until we find otherwise. |
| 133 keyword->clear(); |
| 134 |
| 135 if (match.template_url && |
| 136 TemplateURL::SupportsReplacement(match.template_url) && |
| 137 match.transition == content::PAGE_TRANSITION_KEYWORD) { |
| 138 // The current match is a keyword, return that as the selected keyword. |
| 139 keyword->assign(match.template_url->keyword()); |
| 140 return false; |
| 141 } |
| 142 |
| 143 // See if the current match's fill_into_edit corresponds to a keyword. |
| 144 return GetKeywordForText(match.fill_into_edit, keyword); |
| 145 } |
| 146 |
| 147 bool AutocompletePopupModel::GetKeywordForText(const string16& text, |
| 148 string16* keyword) const { |
| 149 // Creates keyword_hint first in case |keyword| is a pointer to |text|. |
| 150 const string16 keyword_hint(TemplateURLService::CleanUserInputKeyword(text)); |
| 151 |
| 152 // Assume we have no keyword until we find otherwise. |
| 153 keyword->clear(); |
| 154 |
| 155 if (keyword_hint.empty()) |
| 156 return false; |
| 157 Profile* profile = edit_model_->profile(); |
| 158 TemplateURLService* url_service = |
| 159 TemplateURLServiceFactory::GetForProfile(profile); |
| 160 if (!url_service) |
| 161 return false; |
| 162 url_service->Load(); |
| 163 |
| 164 // Don't provide a hint if this keyword doesn't support replacement. |
| 165 const TemplateURL* const template_url = |
| 166 url_service->GetTemplateURLForKeyword(keyword_hint); |
| 167 if (!TemplateURL::SupportsReplacement(template_url)) |
| 168 return false; |
| 169 |
| 170 // Don't provide a hint for inactive/disabled extension keywords. |
| 171 if (template_url->IsExtensionKeyword()) { |
| 172 const Extension* extension = profile->GetExtensionService()-> |
| 173 GetExtensionById(template_url->GetExtensionId(), false); |
| 174 if (!extension || (profile->IsOffTheRecord() && |
| 175 !profile->GetExtensionService()->IsIncognitoEnabled(extension->id()))) |
| 176 return false; |
| 177 } |
| 178 |
| 179 keyword->assign(keyword_hint); |
| 180 return true; |
| 181 } |
| 182 |
138 void AutocompletePopupModel::Move(int count) { | 183 void AutocompletePopupModel::Move(int count) { |
139 const AutocompleteResult& result = this->result(); | 184 const AutocompleteResult& result = this->result(); |
140 if (result.empty()) | 185 if (result.empty()) |
141 return; | 186 return; |
142 | 187 |
143 // The user is using the keyboard to change the selection, so stop tracking | 188 // The user is using the keyboard to change the selection, so stop tracking |
144 // hover. | 189 // hover. |
145 SetHoveredLine(kNoMatch); | 190 SetHoveredLine(kNoMatch); |
146 | 191 |
147 // Clamp the new line to [0, result_.count() - 1]. | 192 // Clamp the new line to [0, result_.count() - 1]. |
148 const size_t new_line = selected_line_ + count; | 193 const size_t new_line = selected_line_ + count; |
149 SetSelectedLine(((count < 0) && (new_line >= selected_line_)) ? 0 : new_line, | 194 SetSelectedLine(((count < 0) && (new_line >= selected_line_)) ? 0 : new_line, |
150 false, false); | 195 false, false); |
151 } | 196 } |
152 | 197 |
153 void AutocompletePopupModel::SetSelectedLineState(LineState state) { | |
154 DCHECK(!result().empty()); | |
155 DCHECK_NE(kNoMatch, selected_line_); | |
156 | |
157 const AutocompleteMatch& match = result().match_at(selected_line_); | |
158 DCHECK(match.associated_keyword.get()); | |
159 | |
160 selected_line_state_ = state; | |
161 view_->InvalidateLine(selected_line_); | |
162 } | |
163 | |
164 void AutocompletePopupModel::TryDeletingCurrentItem() { | 198 void AutocompletePopupModel::TryDeletingCurrentItem() { |
165 // We could use InfoForCurrentSelection() here, but it seems better to try | 199 // We could use InfoForCurrentSelection() here, but it seems better to try |
166 // and shift-delete the actual selection, rather than any "in progress, not | 200 // and shift-delete the actual selection, rather than any "in progress, not |
167 // yet visible" one. | 201 // yet visible" one. |
168 if (selected_line_ == kNoMatch) | 202 if (selected_line_ == kNoMatch) |
169 return; | 203 return; |
170 | 204 |
171 // Cancel the query so the matches don't change on the user. | 205 // Cancel the query so the matches don't change on the user. |
172 autocomplete_controller()->Stop(false); | 206 autocomplete_controller()->Stop(false); |
173 | 207 |
(...skipping 28 matching lines...) Expand all Loading... |
202 match.template_url->GetExtensionId()); | 236 match.template_url->GetExtensionId()); |
203 } | 237 } |
204 | 238 |
205 void AutocompletePopupModel::OnResultChanged() { | 239 void AutocompletePopupModel::OnResultChanged() { |
206 const AutocompleteResult& result = this->result(); | 240 const AutocompleteResult& result = this->result(); |
207 selected_line_ = result.default_match() == result.end() ? | 241 selected_line_ = result.default_match() == result.end() ? |
208 kNoMatch : static_cast<size_t>(result.default_match() - result.begin()); | 242 kNoMatch : static_cast<size_t>(result.default_match() - result.begin()); |
209 // There had better not be a nonempty result set with no default match. | 243 // There had better not be a nonempty result set with no default match. |
210 CHECK((selected_line_ != kNoMatch) || result.empty()); | 244 CHECK((selected_line_ != kNoMatch) || result.empty()); |
211 manually_selected_match_.Clear(); | 245 manually_selected_match_.Clear(); |
212 selected_line_state_ = NORMAL; | |
213 // If we're going to trim the window size to no longer include the hovered | 246 // If we're going to trim the window size to no longer include the hovered |
214 // line, turn hover off. Practically, this shouldn't happen, but it | 247 // line, turn hover off. Practically, this shouldn't happen, but it |
215 // doesn't hurt to be defensive. | 248 // doesn't hurt to be defensive. |
216 if ((hovered_line_ != kNoMatch) && (result.size() <= hovered_line_)) | 249 if ((hovered_line_ != kNoMatch) && (result.size() <= hovered_line_)) |
217 SetHoveredLine(kNoMatch); | 250 SetHoveredLine(kNoMatch); |
218 | 251 |
219 view_->UpdatePopupAppearance(); | 252 view_->UpdatePopupAppearance(); |
220 } | 253 } |
OLD | NEW |