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