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/search_engines/template_url_table_model.h"
8 #include "base/bind_helpers.h"
9 #include "base/i18n/rtl.h"
10 #include "base/stl_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/task/cancelable_task_tracker.h"
13 #include "chrome/browser/favicon/favicon_service.h"
14 #include "chrome/browser/favicon/favicon_service_factory.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/search_engines/template_url.h"
17 #include "chrome/browser/search_engines/template_url_service.h"
18 #include "components/favicon_base/favicon_types.h"
19 #include "grit/generated_resources.h"
20 #include "grit/ui_resources.h"
21 #include "third_party/skia/include/core/SkBitmap.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/base/models/table_model_observer.h"
24 #include "ui/base/resource/resource_bundle.h"
25 #include "ui/gfx/favicon_size.h"
26 #include "ui/gfx/image/image_skia.h"
28 // Group IDs used by TemplateURLTableModel.
29 static const int kMainGroupID = 0;
30 static const int kOtherGroupID = 1;
31 static const int kExtensionGroupID = 2;
33 // ModelEntry ----------------------------------------------------
35 // ModelEntry wraps a TemplateURL as returned from the TemplateURL.
36 // ModelEntry also tracks state information about the URL.
38 // Icon used while loading, or if a specific favicon can't be found.
39 static gfx::ImageSkia* default_icon = NULL;
43 ModelEntry(TemplateURLTableModel* model, TemplateURL* template_url)
44 : template_url_(template_url),
45 load_state_(NOT_LOADED),
48 default_icon = ResourceBundle::GetSharedInstance().
49 GetImageSkiaNamed(IDR_DEFAULT_FAVICON);
53 TemplateURL* template_url() {
57 gfx::ImageSkia GetIcon() {
58 if (load_state_ == NOT_LOADED)
60 if (!favicon_.isNull())
65 // Resets internal status so that the next time the icon is asked for its
66 // fetched again. This should be invoked if the url is modified.
68 load_state_ = NOT_LOADED;
69 favicon_ = gfx::ImageSkia();
73 // State of the favicon.
82 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
83 model_->template_url_service()->profile(), Profile::EXPLICIT_ACCESS);
86 GURL favicon_url = template_url()->favicon_url();
87 if (!favicon_url.is_valid()) {
88 // The favicon url isn't always set. Guess at one here.
89 if (template_url_->url_ref().IsValid()) {
90 GURL url(template_url_->url());
92 favicon_url = TemplateURL::GenerateFaviconURL(url);
94 if (!favicon_url.is_valid())
97 load_state_ = LOADING;
98 favicon_service->GetFaviconImage(
100 favicon_base::FAVICON,
102 base::Bind(&ModelEntry::OnFaviconDataAvailable, base::Unretained(this)),
106 void OnFaviconDataAvailable(
107 const favicon_base::FaviconImageResult& image_result) {
108 load_state_ = LOADED;
109 if (!image_result.image.IsEmpty()) {
110 favicon_ = image_result.image.AsImageSkia();
111 model_->FaviconAvailable(this);
115 TemplateURL* template_url_;
116 gfx::ImageSkia favicon_;
117 LoadState load_state_;
118 TemplateURLTableModel* model_;
119 base::CancelableTaskTracker tracker_;
121 DISALLOW_COPY_AND_ASSIGN(ModelEntry);
124 // TemplateURLTableModel -----------------------------------------
126 TemplateURLTableModel::TemplateURLTableModel(
127 TemplateURLService* template_url_service)
129 template_url_service_(template_url_service) {
130 DCHECK(template_url_service);
131 template_url_service_->Load();
132 template_url_service_->AddObserver(this);
136 TemplateURLTableModel::~TemplateURLTableModel() {
137 template_url_service_->RemoveObserver(this);
138 STLDeleteElements(&entries_);
142 void TemplateURLTableModel::Reload() {
143 STLDeleteElements(&entries_);
146 TemplateURLService::TemplateURLVector urls =
147 template_url_service_->GetTemplateURLs();
149 std::vector<ModelEntry*> default_entries, other_entries, extension_entries;
150 // Keywords that can be made the default first.
151 for (TemplateURLService::TemplateURLVector::iterator i = urls.begin();
152 i != urls.end(); ++i) {
153 TemplateURL* template_url = *i;
154 // NOTE: we don't use ShowInDefaultList here to avoid items bouncing around
155 // the lists while editing.
156 if (template_url->show_in_default_list())
157 default_entries.push_back(new ModelEntry(this, template_url));
158 else if (template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION)
159 extension_entries.push_back(new ModelEntry(this, template_url));
161 other_entries.push_back(new ModelEntry(this, template_url));
164 last_search_engine_index_ = static_cast<int>(default_entries.size());
165 last_other_engine_index_ = last_search_engine_index_ +
166 static_cast<int>(other_entries.size());
168 entries_.insert(entries_.end(),
169 default_entries.begin(),
170 default_entries.end());
172 entries_.insert(entries_.end(),
173 other_entries.begin(),
174 other_entries.end());
176 entries_.insert(entries_.end(),
177 extension_entries.begin(),
178 extension_entries.end());
181 observer_->OnModelChanged();
184 int TemplateURLTableModel::RowCount() {
185 return static_cast<int>(entries_.size());
188 base::string16 TemplateURLTableModel::GetText(int row, int col_id) {
189 DCHECK(row >= 0 && row < RowCount());
190 const TemplateURL* url = entries_[row]->template_url();
191 if (col_id == IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN) {
192 base::string16 url_short_name = url->short_name();
193 // TODO(xji): Consider adding a special case if the short name is a URL,
194 // since those should always be displayed LTR. Please refer to
195 // http://crbug.com/6726 for more information.
196 base::i18n::AdjustStringForLocaleDirection(&url_short_name);
197 return (template_url_service_->GetDefaultSearchProvider() == url) ?
198 l10n_util::GetStringFUTF16(IDS_SEARCH_ENGINES_EDITOR_DEFAULT_ENGINE,
199 url_short_name) : url_short_name;
202 DCHECK_EQ(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN, col_id);
203 // Keyword should be domain name. Force it to have LTR directionality.
204 return base::i18n::GetDisplayStringInLTRDirectionality(url->keyword());
207 gfx::ImageSkia TemplateURLTableModel::GetIcon(int row) {
208 DCHECK(row >= 0 && row < RowCount());
209 return entries_[row]->GetIcon();
212 void TemplateURLTableModel::SetObserver(ui::TableModelObserver* observer) {
213 observer_ = observer;
216 bool TemplateURLTableModel::HasGroups() {
220 TemplateURLTableModel::Groups TemplateURLTableModel::GetGroups() {
223 Group search_engine_group;
224 search_engine_group.title =
225 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAIN_SEPARATOR);
226 search_engine_group.id = kMainGroupID;
227 groups.push_back(search_engine_group);
231 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR);
232 other_group.id = kOtherGroupID;
233 groups.push_back(other_group);
235 Group extension_group;
236 extension_group.title =
237 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_EXTENSIONS_SEPARATOR);
238 extension_group.id = kExtensionGroupID;
239 groups.push_back(extension_group);
244 int TemplateURLTableModel::GetGroupID(int row) {
245 DCHECK(row >= 0 && row < RowCount());
246 if (row < last_search_engine_index_)
248 return row < last_other_engine_index_ ? kOtherGroupID : kExtensionGroupID;
251 void TemplateURLTableModel::Remove(int index) {
252 // Remove the observer while we modify the model, that way we don't need to
253 // worry about the model calling us back when we mutate it.
254 template_url_service_->RemoveObserver(this);
255 TemplateURL* template_url = GetTemplateURL(index);
257 scoped_ptr<ModelEntry> entry(RemoveEntry(index));
259 // Make sure to remove from the table model first, otherwise the
260 // TemplateURL would be freed.
261 template_url_service_->Remove(template_url);
262 template_url_service_->AddObserver(this);
265 void TemplateURLTableModel::Add(int index,
266 const base::string16& short_name,
267 const base::string16& keyword,
268 const std::string& url) {
269 DCHECK(index >= 0 && index <= RowCount());
270 DCHECK(!url.empty());
271 template_url_service_->RemoveObserver(this);
272 TemplateURLData data;
273 data.short_name = short_name;
274 data.SetKeyword(keyword);
276 TemplateURL* turl = new TemplateURL(template_url_service_->profile(), data);
277 template_url_service_->Add(turl);
278 scoped_ptr<ModelEntry> entry(new ModelEntry(this, turl));
279 template_url_service_->AddObserver(this);
280 AddEntry(index, entry.Pass());
283 void TemplateURLTableModel::ModifyTemplateURL(int index,
284 const base::string16& title,
285 const base::string16& keyword,
286 const std::string& url) {
287 DCHECK(index >= 0 && index <= RowCount());
288 DCHECK(!url.empty());
289 TemplateURL* template_url = GetTemplateURL(index);
290 // The default search provider should support replacement.
291 DCHECK(template_url_service_->GetDefaultSearchProvider() != template_url ||
292 template_url->SupportsReplacement());
293 template_url_service_->RemoveObserver(this);
294 template_url_service_->ResetTemplateURL(template_url, title, keyword, url);
295 template_url_service_->AddObserver(this);
296 ReloadIcon(index); // Also calls NotifyChanged().
299 void TemplateURLTableModel::ReloadIcon(int index) {
300 DCHECK(index >= 0 && index < RowCount());
302 entries_[index]->ResetIcon();
304 NotifyChanged(index);
307 TemplateURL* TemplateURLTableModel::GetTemplateURL(int index) {
308 return entries_[index]->template_url();
311 int TemplateURLTableModel::IndexOfTemplateURL(
312 const TemplateURL* template_url) {
313 for (std::vector<ModelEntry*>::iterator i = entries_.begin();
314 i != entries_.end(); ++i) {
315 ModelEntry* entry = *i;
316 if (entry->template_url() == template_url)
317 return static_cast<int>(i - entries_.begin());
322 int TemplateURLTableModel::MoveToMainGroup(int index) {
323 if (index < last_search_engine_index_)
324 return index; // Already in the main group.
326 scoped_ptr<ModelEntry> current_entry(RemoveEntry(index));
327 const int new_index = last_search_engine_index_++;
328 AddEntry(new_index, current_entry.Pass());
332 int TemplateURLTableModel::MakeDefaultTemplateURL(int index) {
333 if (index < 0 || index >= RowCount()) {
338 TemplateURL* keyword = GetTemplateURL(index);
339 const TemplateURL* current_default =
340 template_url_service_->GetDefaultSearchProvider();
341 if (current_default == keyword)
344 template_url_service_->RemoveObserver(this);
345 template_url_service_->SetUserSelectedDefaultSearchProvider(keyword);
346 template_url_service_->AddObserver(this);
348 // The formatting of the default engine is different; notify the table that
349 // both old and new entries have changed.
350 if (current_default != NULL) {
351 int old_index = IndexOfTemplateURL(current_default);
352 // current_default may not be in the list of TemplateURLs if the database is
353 // corrupt and the default TemplateURL is used from preferences
355 NotifyChanged(old_index);
357 const int new_index = IndexOfTemplateURL(keyword);
358 NotifyChanged(new_index);
360 // Make sure the new default is in the main group.
361 return MoveToMainGroup(index);
364 void TemplateURLTableModel::NotifyChanged(int index) {
367 observer_->OnItemsChanged(index, 1);
371 void TemplateURLTableModel::FaviconAvailable(ModelEntry* entry) {
372 std::vector<ModelEntry*>::iterator i =
373 std::find(entries_.begin(), entries_.end(), entry);
374 DCHECK(i != entries_.end());
375 NotifyChanged(static_cast<int>(i - entries_.begin()));
378 void TemplateURLTableModel::OnTemplateURLServiceChanged() {
382 scoped_ptr<ModelEntry> TemplateURLTableModel::RemoveEntry(int index) {
383 scoped_ptr<ModelEntry> entry(entries_[index]);
384 entries_.erase(index + entries_.begin());
385 if (index < last_search_engine_index_)
386 --last_search_engine_index_;
387 if (index < last_other_engine_index_)
388 --last_other_engine_index_;
390 observer_->OnItemsRemoved(index, 1);
394 void TemplateURLTableModel::AddEntry(int index, scoped_ptr<ModelEntry> entry) {
395 entries_.insert(entries_.begin() + index, entry.release());
396 if (index <= last_other_engine_index_)
397 ++last_other_engine_index_;
399 observer_->OnItemsAdded(index, 1);