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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ui/search_engines/template_url_table_model.h"
6
7 #include "base/callback.h"
8 #include "base/i18n/rtl.h"
9 #include "base/stl_util-inl.h"
10 #include "base/utf_string_conversions.h"
11 #include "chrome/browser/favicon_service.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/search_engines/template_url.h"
14 #include "chrome/browser/search_engines/template_url_model.h"
15 #include "grit/app_resources.h"
16 #include "grit/generated_resources.h"
17 #include "third_party/skia/include/core/SkBitmap.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/models/table_model_observer.h"
20 #include "ui/base/resource/resource_bundle.h"
21 #include "ui/gfx/codec/png_codec.h"
22
23 // Group IDs used by TemplateURLTableModel.
24 static const int kMainGroupID = 0;
25 static const int kOtherGroupID = 1;
26
27 // ModelEntry ----------------------------------------------------
28
29 // ModelEntry wraps a TemplateURL as returned from the TemplateURL.
30 // ModelEntry also tracks state information about the URL.
31
32 // Icon used while loading, or if a specific favicon can't be found.
33 static SkBitmap* default_icon = NULL;
34
35 class ModelEntry {
36 public:
ModelEntry(TemplateURLTableModel * model,const TemplateURL & template_url)37 explicit ModelEntry(TemplateURLTableModel* model,
38 const TemplateURL& template_url)
39 : template_url_(template_url),
40 load_state_(NOT_LOADED),
41 model_(model) {
42 if (!default_icon) {
43 default_icon = ResourceBundle::GetSharedInstance().
44 GetBitmapNamed(IDR_DEFAULT_FAVICON);
45 }
46 }
47
template_url()48 const TemplateURL& template_url() {
49 return template_url_;
50 }
51
GetIcon()52 SkBitmap GetIcon() {
53 if (load_state_ == NOT_LOADED)
54 LoadFavicon();
55 if (!favicon_.isNull())
56 return favicon_;
57 return *default_icon;
58 }
59
60 // Resets internal status so that the next time the icon is asked for its
61 // fetched again. This should be invoked if the url is modified.
ResetIcon()62 void ResetIcon() {
63 load_state_ = NOT_LOADED;
64 favicon_ = SkBitmap();
65 }
66
67 private:
68 // State of the favicon.
69 enum LoadState {
70 NOT_LOADED,
71 LOADING,
72 LOADED
73 };
74
LoadFavicon()75 void LoadFavicon() {
76 load_state_ = LOADED;
77 FaviconService* favicon_service =
78 model_->template_url_model()->profile()->GetFaviconService(
79 Profile::EXPLICIT_ACCESS);
80 if (!favicon_service)
81 return;
82 GURL favicon_url = template_url().GetFaviconURL();
83 if (!favicon_url.is_valid()) {
84 // The favicon url isn't always set. Guess at one here.
85 if (template_url_.url() && template_url_.url()->IsValid()) {
86 GURL url = GURL(template_url_.url()->url());
87 if (url.is_valid())
88 favicon_url = TemplateURL::GenerateFaviconURL(url);
89 }
90 if (!favicon_url.is_valid())
91 return;
92 }
93 load_state_ = LOADING;
94 favicon_service->GetFavicon(favicon_url, history::FAVICON,
95 &request_consumer_,
96 NewCallback(this, &ModelEntry::OnFaviconDataAvailable));
97 }
98
OnFaviconDataAvailable(FaviconService::Handle handle,history::FaviconData favicon)99 void OnFaviconDataAvailable(
100 FaviconService::Handle handle,
101 history::FaviconData favicon) {
102 load_state_ = LOADED;
103 if (favicon.is_valid() && gfx::PNGCodec::Decode(favicon.image_data->front(),
104 favicon.image_data->size(),
105 &favicon_)) {
106 model_->FaviconAvailable(this);
107 }
108 }
109
110 const TemplateURL& template_url_;
111 SkBitmap favicon_;
112 LoadState load_state_;
113 TemplateURLTableModel* model_;
114 CancelableRequestConsumer request_consumer_;
115
116 DISALLOW_COPY_AND_ASSIGN(ModelEntry);
117 };
118
119 // TemplateURLTableModel -----------------------------------------
120
TemplateURLTableModel(TemplateURLModel * template_url_model)121 TemplateURLTableModel::TemplateURLTableModel(
122 TemplateURLModel* template_url_model)
123 : observer_(NULL),
124 template_url_model_(template_url_model) {
125 DCHECK(template_url_model);
126 template_url_model_->Load();
127 template_url_model_->AddObserver(this);
128 Reload();
129 }
130
~TemplateURLTableModel()131 TemplateURLTableModel::~TemplateURLTableModel() {
132 template_url_model_->RemoveObserver(this);
133 STLDeleteElements(&entries_);
134 entries_.clear();
135 }
136
Reload()137 void TemplateURLTableModel::Reload() {
138 STLDeleteElements(&entries_);
139 entries_.clear();
140
141 std::vector<const TemplateURL*> urls = template_url_model_->GetTemplateURLs();
142
143 // Keywords that can be made the default first.
144 for (std::vector<const TemplateURL*>::iterator i = urls.begin();
145 i != urls.end(); ++i) {
146 const TemplateURL& template_url = *(*i);
147 // NOTE: we don't use ShowInDefaultList here to avoid items bouncing around
148 // the lists while editing.
149 if (template_url.show_in_default_list())
150 entries_.push_back(new ModelEntry(this, template_url));
151 }
152
153 last_search_engine_index_ = static_cast<int>(entries_.size());
154
155 // Then the rest.
156 for (std::vector<const TemplateURL*>::iterator i = urls.begin();
157 i != urls.end(); ++i) {
158 const TemplateURL* template_url = *i;
159 // NOTE: we don't use ShowInDefaultList here to avoid things bouncing
160 // the lists while editing.
161 if (!template_url->show_in_default_list() &&
162 !template_url->IsExtensionKeyword()) {
163 entries_.push_back(new ModelEntry(this, *template_url));
164 }
165 }
166
167 if (observer_)
168 observer_->OnModelChanged();
169 }
170
RowCount()171 int TemplateURLTableModel::RowCount() {
172 return static_cast<int>(entries_.size());
173 }
174
GetText(int row,int col_id)175 string16 TemplateURLTableModel::GetText(int row, int col_id) {
176 DCHECK(row >= 0 && row < RowCount());
177 const TemplateURL& url = entries_[row]->template_url();
178 if (col_id == IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN) {
179 string16 url_short_name = url.short_name();
180 // TODO(xji): Consider adding a special case if the short name is a URL,
181 // since those should always be displayed LTR. Please refer to
182 // http://crbug.com/6726 for more information.
183 base::i18n::AdjustStringForLocaleDirection(&url_short_name);
184 if (template_url_model_->GetDefaultSearchProvider() == &url) {
185 return l10n_util::GetStringFUTF16(
186 IDS_SEARCH_ENGINES_EDITOR_DEFAULT_ENGINE,
187 url_short_name);
188 }
189 return url_short_name;
190 } else if (col_id == IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN) {
191 // Keyword should be domain name. Force it to have LTR directionality.
192 string16 keyword = url.keyword();
193 keyword = base::i18n::GetDisplayStringInLTRDirectionality(keyword);
194 return keyword;
195 } else {
196 NOTREACHED();
197 return string16();
198 }
199 }
200
GetIcon(int row)201 SkBitmap TemplateURLTableModel::GetIcon(int row) {
202 DCHECK(row >= 0 && row < RowCount());
203 return entries_[row]->GetIcon();
204 }
205
SetObserver(ui::TableModelObserver * observer)206 void TemplateURLTableModel::SetObserver(ui::TableModelObserver* observer) {
207 observer_ = observer;
208 }
209
HasGroups()210 bool TemplateURLTableModel::HasGroups() {
211 return true;
212 }
213
GetGroups()214 TemplateURLTableModel::Groups TemplateURLTableModel::GetGroups() {
215 Groups groups;
216
217 Group search_engine_group;
218 search_engine_group.title =
219 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAIN_SEPARATOR);
220 search_engine_group.id = kMainGroupID;
221 groups.push_back(search_engine_group);
222
223 Group other_group;
224 other_group.title =
225 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR);
226 other_group.id = kOtherGroupID;
227 groups.push_back(other_group);
228
229 return groups;
230 }
231
GetGroupID(int row)232 int TemplateURLTableModel::GetGroupID(int row) {
233 DCHECK(row >= 0 && row < RowCount());
234 return row < last_search_engine_index_ ? kMainGroupID : kOtherGroupID;
235 }
236
Remove(int index)237 void TemplateURLTableModel::Remove(int index) {
238 // Remove the observer while we modify the model, that way we don't need to
239 // worry about the model calling us back when we mutate it.
240 template_url_model_->RemoveObserver(this);
241 const TemplateURL* template_url = &GetTemplateURL(index);
242
243 scoped_ptr<ModelEntry> entry(entries_[index]);
244 entries_.erase(entries_.begin() + index);
245 if (index < last_search_engine_index_)
246 last_search_engine_index_--;
247 if (observer_)
248 observer_->OnItemsRemoved(index, 1);
249
250 // Make sure to remove from the table model first, otherwise the
251 // TemplateURL would be freed.
252 template_url_model_->Remove(template_url);
253 template_url_model_->AddObserver(this);
254 }
255
Add(int index,TemplateURL * template_url)256 void TemplateURLTableModel::Add(int index, TemplateURL* template_url) {
257 DCHECK(index >= 0 && index <= RowCount());
258 ModelEntry* entry = new ModelEntry(this, *template_url);
259 entries_.insert(entries_.begin() + index, entry);
260 if (observer_)
261 observer_->OnItemsAdded(index, 1);
262 template_url_model_->RemoveObserver(this);
263 template_url_model_->Add(template_url);
264 template_url_model_->AddObserver(this);
265 }
266
ModifyTemplateURL(int index,const string16 & title,const string16 & keyword,const std::string & url)267 void TemplateURLTableModel::ModifyTemplateURL(int index,
268 const string16& title,
269 const string16& keyword,
270 const std::string& url) {
271 DCHECK(index >= 0 && index <= RowCount());
272 const TemplateURL* template_url = &GetTemplateURL(index);
273 template_url_model_->RemoveObserver(this);
274 template_url_model_->ResetTemplateURL(template_url, title, keyword, url);
275 if (template_url_model_->GetDefaultSearchProvider() == template_url &&
276 !TemplateURL::SupportsReplacement(template_url)) {
277 // The entry was the default search provider, but the url has been modified
278 // so that it no longer supports replacement. Reset the default search
279 // provider so that it doesn't point to a bogus entry.
280 template_url_model_->SetDefaultSearchProvider(NULL);
281 }
282 template_url_model_->AddObserver(this);
283 ReloadIcon(index); // Also calls NotifyChanged().
284 }
285
ReloadIcon(int index)286 void TemplateURLTableModel::ReloadIcon(int index) {
287 DCHECK(index >= 0 && index < RowCount());
288
289 entries_[index]->ResetIcon();
290
291 NotifyChanged(index);
292 }
293
GetTemplateURL(int index)294 const TemplateURL& TemplateURLTableModel::GetTemplateURL(int index) {
295 return entries_[index]->template_url();
296 }
297
IndexOfTemplateURL(const TemplateURL * template_url)298 int TemplateURLTableModel::IndexOfTemplateURL(
299 const TemplateURL* template_url) {
300 for (std::vector<ModelEntry*>::iterator i = entries_.begin();
301 i != entries_.end(); ++i) {
302 ModelEntry* entry = *i;
303 if (&(entry->template_url()) == template_url)
304 return static_cast<int>(i - entries_.begin());
305 }
306 return -1;
307 }
308
MoveToMainGroup(int index)309 int TemplateURLTableModel::MoveToMainGroup(int index) {
310 if (index < last_search_engine_index_)
311 return index; // Already in the main group.
312
313 ModelEntry* current_entry = entries_[index];
314 entries_.erase(index + entries_.begin());
315 if (observer_)
316 observer_->OnItemsRemoved(index, 1);
317
318 const int new_index = last_search_engine_index_++;
319 entries_.insert(entries_.begin() + new_index, current_entry);
320 if (observer_)
321 observer_->OnItemsAdded(new_index, 1);
322 return new_index;
323 }
324
MakeDefaultTemplateURL(int index)325 int TemplateURLTableModel::MakeDefaultTemplateURL(int index) {
326 if (index < 0 || index >= RowCount()) {
327 NOTREACHED();
328 return -1;
329 }
330
331 const TemplateURL* keyword = &GetTemplateURL(index);
332 const TemplateURL* current_default =
333 template_url_model_->GetDefaultSearchProvider();
334 if (current_default == keyword)
335 return -1;
336
337 template_url_model_->RemoveObserver(this);
338 template_url_model_->SetDefaultSearchProvider(keyword);
339 template_url_model_->AddObserver(this);
340
341 // The formatting of the default engine is different; notify the table that
342 // both old and new entries have changed.
343 if (current_default != NULL) {
344 int old_index = IndexOfTemplateURL(current_default);
345 // current_default may not be in the list of TemplateURLs if the database is
346 // corrupt and the default TemplateURL is used from preferences
347 if (old_index >= 0)
348 NotifyChanged(old_index);
349 }
350 const int new_index = IndexOfTemplateURL(keyword);
351 NotifyChanged(new_index);
352
353 // Make sure the new default is in the main group.
354 return MoveToMainGroup(index);
355 }
356
NotifyChanged(int index)357 void TemplateURLTableModel::NotifyChanged(int index) {
358 if (observer_) {
359 DCHECK_GE(index, 0);
360 observer_->OnItemsChanged(index, 1);
361 }
362 }
363
FaviconAvailable(ModelEntry * entry)364 void TemplateURLTableModel::FaviconAvailable(ModelEntry* entry) {
365 std::vector<ModelEntry*>::iterator i =
366 find(entries_.begin(), entries_.end(), entry);
367 DCHECK(i != entries_.end());
368 NotifyChanged(static_cast<int>(i - entries_.begin()));
369 }
370
OnTemplateURLModelChanged()371 void TemplateURLTableModel::OnTemplateURLModelChanged() {
372 Reload();
373 }
374