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.
24static const int kMainGroupID = 0;
25static 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.
33static SkBitmap* default_icon = NULL;
34
35class ModelEntry {
36 public:
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
48  const TemplateURL& template_url() {
49    return template_url_;
50  }
51
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.
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
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
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
121TemplateURLTableModel::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
131TemplateURLTableModel::~TemplateURLTableModel() {
132  template_url_model_->RemoveObserver(this);
133  STLDeleteElements(&entries_);
134  entries_.clear();
135}
136
137void 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
171int TemplateURLTableModel::RowCount() {
172  return static_cast<int>(entries_.size());
173}
174
175string16 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
201SkBitmap TemplateURLTableModel::GetIcon(int row) {
202  DCHECK(row >= 0 && row < RowCount());
203  return entries_[row]->GetIcon();
204}
205
206void TemplateURLTableModel::SetObserver(ui::TableModelObserver* observer) {
207  observer_ = observer;
208}
209
210bool TemplateURLTableModel::HasGroups() {
211  return true;
212}
213
214TemplateURLTableModel::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
232int TemplateURLTableModel::GetGroupID(int row) {
233  DCHECK(row >= 0 && row < RowCount());
234  return row < last_search_engine_index_ ? kMainGroupID : kOtherGroupID;
235}
236
237void 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
256void 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
267void 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
286void TemplateURLTableModel::ReloadIcon(int index) {
287  DCHECK(index >= 0 && index < RowCount());
288
289  entries_[index]->ResetIcon();
290
291  NotifyChanged(index);
292}
293
294const TemplateURL& TemplateURLTableModel::GetTemplateURL(int index) {
295  return entries_[index]->template_url();
296}
297
298int 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
309int 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
325int 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
357void TemplateURLTableModel::NotifyChanged(int index) {
358  if (observer_) {
359    DCHECK_GE(index, 0);
360    observer_->OnItemsChanged(index, 1);
361  }
362}
363
364void 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
371void TemplateURLTableModel::OnTemplateURLModelChanged() {
372  Reload();
373}
374