• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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