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.
4
5 #include "chrome/browser/ui/search_engines/template_url_table_model.h"
6
7 #include "base/bind.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"
27
28 // Group IDs used by TemplateURLTableModel.
29 static const int kMainGroupID = 0;
30 static const int kOtherGroupID = 1;
31 static const int kExtensionGroupID = 2;
32
33 // ModelEntry ----------------------------------------------------
34
35 // ModelEntry wraps a TemplateURL as returned from the TemplateURL.
36 // ModelEntry also tracks state information about the URL.
37
38 // Icon used while loading, or if a specific favicon can't be found.
39 static const gfx::ImageSkia* default_icon = NULL;
40
41 class ModelEntry {
42 public:
ModelEntry(TemplateURLTableModel * model,TemplateURL * template_url)43 ModelEntry(TemplateURLTableModel* model, TemplateURL* template_url)
44 : template_url_(template_url),
45 load_state_(NOT_LOADED),
46 model_(model) {
47 if (!default_icon) {
48 default_icon = ResourceBundle::GetSharedInstance().
49 GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToImageSkia();
50 }
51 }
52
template_url()53 TemplateURL* template_url() {
54 return template_url_;
55 }
56
GetIcon()57 gfx::ImageSkia GetIcon() {
58 if (load_state_ == NOT_LOADED)
59 LoadFavicon();
60 if (!favicon_.isNull())
61 return favicon_;
62 return *default_icon;
63 }
64
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.
ResetIcon()67 void ResetIcon() {
68 load_state_ = NOT_LOADED;
69 favicon_ = gfx::ImageSkia();
70 }
71
72 private:
73 // State of the favicon.
74 enum LoadState {
75 NOT_LOADED,
76 LOADING,
77 LOADED
78 };
79
LoadFavicon()80 void LoadFavicon() {
81 load_state_ = LOADED;
82 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
83 model_->template_url_service()->profile(), Profile::EXPLICIT_ACCESS);
84 if (!favicon_service)
85 return;
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 model_->template_url_service()->search_terms_data())) {
91 GURL url(template_url_->url());
92 if (url.is_valid())
93 favicon_url = TemplateURL::GenerateFaviconURL(url);
94 }
95 if (!favicon_url.is_valid())
96 return;
97 }
98 load_state_ = LOADING;
99 favicon_service->GetFaviconImage(
100 favicon_url,
101 favicon_base::FAVICON,
102 gfx::kFaviconSize,
103 base::Bind(&ModelEntry::OnFaviconDataAvailable, base::Unretained(this)),
104 &tracker_);
105 }
106
OnFaviconDataAvailable(const favicon_base::FaviconImageResult & image_result)107 void OnFaviconDataAvailable(
108 const favicon_base::FaviconImageResult& image_result) {
109 load_state_ = LOADED;
110 if (!image_result.image.IsEmpty()) {
111 favicon_ = image_result.image.AsImageSkia();
112 model_->FaviconAvailable(this);
113 }
114 }
115
116 TemplateURL* template_url_;
117 gfx::ImageSkia favicon_;
118 LoadState load_state_;
119 TemplateURLTableModel* model_;
120 base::CancelableTaskTracker tracker_;
121
122 DISALLOW_COPY_AND_ASSIGN(ModelEntry);
123 };
124
125 // TemplateURLTableModel -----------------------------------------
126
TemplateURLTableModel(TemplateURLService * template_url_service)127 TemplateURLTableModel::TemplateURLTableModel(
128 TemplateURLService* template_url_service)
129 : observer_(NULL),
130 template_url_service_(template_url_service) {
131 DCHECK(template_url_service);
132 template_url_service_->Load();
133 template_url_service_->AddObserver(this);
134 Reload();
135 }
136
~TemplateURLTableModel()137 TemplateURLTableModel::~TemplateURLTableModel() {
138 template_url_service_->RemoveObserver(this);
139 STLDeleteElements(&entries_);
140 entries_.clear();
141 }
142
Reload()143 void TemplateURLTableModel::Reload() {
144 STLDeleteElements(&entries_);
145 entries_.clear();
146
147 TemplateURLService::TemplateURLVector urls =
148 template_url_service_->GetTemplateURLs();
149
150 std::vector<ModelEntry*> default_entries, other_entries, extension_entries;
151 // Keywords that can be made the default first.
152 for (TemplateURLService::TemplateURLVector::iterator i = urls.begin();
153 i != urls.end(); ++i) {
154 TemplateURL* template_url = *i;
155 // NOTE: we don't use ShowInDefaultList here to avoid items bouncing around
156 // the lists while editing.
157 if (template_url->show_in_default_list())
158 default_entries.push_back(new ModelEntry(this, template_url));
159 else if (template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION)
160 extension_entries.push_back(new ModelEntry(this, template_url));
161 else
162 other_entries.push_back(new ModelEntry(this, template_url));
163 }
164
165 last_search_engine_index_ = static_cast<int>(default_entries.size());
166 last_other_engine_index_ = last_search_engine_index_ +
167 static_cast<int>(other_entries.size());
168
169 entries_.insert(entries_.end(),
170 default_entries.begin(),
171 default_entries.end());
172
173 entries_.insert(entries_.end(),
174 other_entries.begin(),
175 other_entries.end());
176
177 entries_.insert(entries_.end(),
178 extension_entries.begin(),
179 extension_entries.end());
180
181 if (observer_)
182 observer_->OnModelChanged();
183 }
184
RowCount()185 int TemplateURLTableModel::RowCount() {
186 return static_cast<int>(entries_.size());
187 }
188
GetText(int row,int col_id)189 base::string16 TemplateURLTableModel::GetText(int row, int col_id) {
190 DCHECK(row >= 0 && row < RowCount());
191 const TemplateURL* url = entries_[row]->template_url();
192 if (col_id == IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN) {
193 base::string16 url_short_name = url->short_name();
194 // TODO(xji): Consider adding a special case if the short name is a URL,
195 // since those should always be displayed LTR. Please refer to
196 // http://crbug.com/6726 for more information.
197 base::i18n::AdjustStringForLocaleDirection(&url_short_name);
198 return (template_url_service_->GetDefaultSearchProvider() == url) ?
199 l10n_util::GetStringFUTF16(IDS_SEARCH_ENGINES_EDITOR_DEFAULT_ENGINE,
200 url_short_name) : url_short_name;
201 }
202
203 DCHECK_EQ(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN, col_id);
204 // Keyword should be domain name. Force it to have LTR directionality.
205 return base::i18n::GetDisplayStringInLTRDirectionality(url->keyword());
206 }
207
GetIcon(int row)208 gfx::ImageSkia TemplateURLTableModel::GetIcon(int row) {
209 DCHECK(row >= 0 && row < RowCount());
210 return entries_[row]->GetIcon();
211 }
212
SetObserver(ui::TableModelObserver * observer)213 void TemplateURLTableModel::SetObserver(ui::TableModelObserver* observer) {
214 observer_ = observer;
215 }
216
HasGroups()217 bool TemplateURLTableModel::HasGroups() {
218 return true;
219 }
220
GetGroups()221 TemplateURLTableModel::Groups TemplateURLTableModel::GetGroups() {
222 Groups groups;
223
224 Group search_engine_group;
225 search_engine_group.title =
226 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAIN_SEPARATOR);
227 search_engine_group.id = kMainGroupID;
228 groups.push_back(search_engine_group);
229
230 Group other_group;
231 other_group.title =
232 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR);
233 other_group.id = kOtherGroupID;
234 groups.push_back(other_group);
235
236 Group extension_group;
237 extension_group.title =
238 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_EXTENSIONS_SEPARATOR);
239 extension_group.id = kExtensionGroupID;
240 groups.push_back(extension_group);
241
242 return groups;
243 }
244
GetGroupID(int row)245 int TemplateURLTableModel::GetGroupID(int row) {
246 DCHECK(row >= 0 && row < RowCount());
247 if (row < last_search_engine_index_)
248 return kMainGroupID;
249 return row < last_other_engine_index_ ? kOtherGroupID : kExtensionGroupID;
250 }
251
Remove(int index)252 void TemplateURLTableModel::Remove(int index) {
253 // Remove the observer while we modify the model, that way we don't need to
254 // worry about the model calling us back when we mutate it.
255 template_url_service_->RemoveObserver(this);
256 TemplateURL* template_url = GetTemplateURL(index);
257
258 scoped_ptr<ModelEntry> entry(RemoveEntry(index));
259
260 // Make sure to remove from the table model first, otherwise the
261 // TemplateURL would be freed.
262 template_url_service_->Remove(template_url);
263 template_url_service_->AddObserver(this);
264 }
265
Add(int index,const base::string16 & short_name,const base::string16 & keyword,const std::string & url)266 void TemplateURLTableModel::Add(int index,
267 const base::string16& short_name,
268 const base::string16& keyword,
269 const std::string& url) {
270 DCHECK(index >= 0 && index <= RowCount());
271 DCHECK(!url.empty());
272 template_url_service_->RemoveObserver(this);
273 TemplateURLData data;
274 data.short_name = short_name;
275 data.SetKeyword(keyword);
276 data.SetURL(url);
277 TemplateURL* turl = new TemplateURL(data);
278 template_url_service_->Add(turl);
279 scoped_ptr<ModelEntry> entry(new ModelEntry(this, turl));
280 template_url_service_->AddObserver(this);
281 AddEntry(index, entry.Pass());
282 }
283
ModifyTemplateURL(int index,const base::string16 & title,const base::string16 & keyword,const std::string & url)284 void TemplateURLTableModel::ModifyTemplateURL(int index,
285 const base::string16& title,
286 const base::string16& keyword,
287 const std::string& url) {
288 DCHECK(index >= 0 && index <= RowCount());
289 DCHECK(!url.empty());
290 TemplateURL* template_url = GetTemplateURL(index);
291 // The default search provider should support replacement.
292 DCHECK(template_url_service_->GetDefaultSearchProvider() != template_url ||
293 template_url->SupportsReplacement(
294 template_url_service_->search_terms_data()));
295 template_url_service_->RemoveObserver(this);
296 template_url_service_->ResetTemplateURL(template_url, title, keyword, url);
297 template_url_service_->AddObserver(this);
298 ReloadIcon(index); // Also calls NotifyChanged().
299 }
300
ReloadIcon(int index)301 void TemplateURLTableModel::ReloadIcon(int index) {
302 DCHECK(index >= 0 && index < RowCount());
303
304 entries_[index]->ResetIcon();
305
306 NotifyChanged(index);
307 }
308
GetTemplateURL(int index)309 TemplateURL* TemplateURLTableModel::GetTemplateURL(int index) {
310 return entries_[index]->template_url();
311 }
312
IndexOfTemplateURL(const TemplateURL * template_url)313 int TemplateURLTableModel::IndexOfTemplateURL(
314 const TemplateURL* template_url) {
315 for (std::vector<ModelEntry*>::iterator i = entries_.begin();
316 i != entries_.end(); ++i) {
317 ModelEntry* entry = *i;
318 if (entry->template_url() == template_url)
319 return static_cast<int>(i - entries_.begin());
320 }
321 return -1;
322 }
323
MoveToMainGroup(int index)324 int TemplateURLTableModel::MoveToMainGroup(int index) {
325 if (index < last_search_engine_index_)
326 return index; // Already in the main group.
327
328 scoped_ptr<ModelEntry> current_entry(RemoveEntry(index));
329 const int new_index = last_search_engine_index_++;
330 AddEntry(new_index, current_entry.Pass());
331 return new_index;
332 }
333
MakeDefaultTemplateURL(int index)334 int TemplateURLTableModel::MakeDefaultTemplateURL(int index) {
335 if (index < 0 || index >= RowCount()) {
336 NOTREACHED();
337 return -1;
338 }
339
340 TemplateURL* keyword = GetTemplateURL(index);
341 const TemplateURL* current_default =
342 template_url_service_->GetDefaultSearchProvider();
343 if (current_default == keyword)
344 return -1;
345
346 template_url_service_->RemoveObserver(this);
347 template_url_service_->SetUserSelectedDefaultSearchProvider(keyword);
348 template_url_service_->AddObserver(this);
349
350 // The formatting of the default engine is different; notify the table that
351 // both old and new entries have changed.
352 if (current_default != NULL) {
353 int old_index = IndexOfTemplateURL(current_default);
354 // current_default may not be in the list of TemplateURLs if the database is
355 // corrupt and the default TemplateURL is used from preferences
356 if (old_index >= 0)
357 NotifyChanged(old_index);
358 }
359 const int new_index = IndexOfTemplateURL(keyword);
360 NotifyChanged(new_index);
361
362 // Make sure the new default is in the main group.
363 return MoveToMainGroup(index);
364 }
365
NotifyChanged(int index)366 void TemplateURLTableModel::NotifyChanged(int index) {
367 if (observer_) {
368 DCHECK_GE(index, 0);
369 observer_->OnItemsChanged(index, 1);
370 }
371 }
372
FaviconAvailable(ModelEntry * entry)373 void TemplateURLTableModel::FaviconAvailable(ModelEntry* entry) {
374 std::vector<ModelEntry*>::iterator i =
375 std::find(entries_.begin(), entries_.end(), entry);
376 DCHECK(i != entries_.end());
377 NotifyChanged(static_cast<int>(i - entries_.begin()));
378 }
379
OnTemplateURLServiceChanged()380 void TemplateURLTableModel::OnTemplateURLServiceChanged() {
381 Reload();
382 }
383
RemoveEntry(int index)384 scoped_ptr<ModelEntry> TemplateURLTableModel::RemoveEntry(int index) {
385 scoped_ptr<ModelEntry> entry(entries_[index]);
386 entries_.erase(index + entries_.begin());
387 if (index < last_search_engine_index_)
388 --last_search_engine_index_;
389 if (index < last_other_engine_index_)
390 --last_other_engine_index_;
391 if (observer_)
392 observer_->OnItemsRemoved(index, 1);
393 return entry.Pass();
394 }
395
AddEntry(int index,scoped_ptr<ModelEntry> entry)396 void TemplateURLTableModel::AddEntry(int index, scoped_ptr<ModelEntry> entry) {
397 entries_.insert(entries_.begin() + index, entry.release());
398 if (index <= last_other_engine_index_)
399 ++last_other_engine_index_;
400 if (observer_)
401 observer_->OnItemsAdded(index, 1);
402 }
403