Home | History | Annotate | Download | only in search_engines
      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:
     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 
     53   TemplateURL* template_url() {
     54     return template_url_;
     55   }
     56 
     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.
     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 
     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 
    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 
    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 
    137 TemplateURLTableModel::~TemplateURLTableModel() {
    138   template_url_service_->RemoveObserver(this);
    139   STLDeleteElements(&entries_);
    140   entries_.clear();
    141 }
    142 
    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 
    185 int TemplateURLTableModel::RowCount() {
    186   return static_cast<int>(entries_.size());
    187 }
    188 
    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 
    208 gfx::ImageSkia TemplateURLTableModel::GetIcon(int row) {
    209   DCHECK(row >= 0 && row < RowCount());
    210   return entries_[row]->GetIcon();
    211 }
    212 
    213 void TemplateURLTableModel::SetObserver(ui::TableModelObserver* observer) {
    214   observer_ = observer;
    215 }
    216 
    217 bool TemplateURLTableModel::HasGroups() {
    218   return true;
    219 }
    220 
    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 
    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 
    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 
    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 
    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 
    301 void TemplateURLTableModel::ReloadIcon(int index) {
    302   DCHECK(index >= 0 && index < RowCount());
    303 
    304   entries_[index]->ResetIcon();
    305 
    306   NotifyChanged(index);
    307 }
    308 
    309 TemplateURL* TemplateURLTableModel::GetTemplateURL(int index) {
    310   return entries_[index]->template_url();
    311 }
    312 
    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 
    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 
    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 
    366 void TemplateURLTableModel::NotifyChanged(int index) {
    367   if (observer_) {
    368     DCHECK_GE(index, 0);
    369     observer_->OnItemsChanged(index, 1);
    370   }
    371 }
    372 
    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 
    380 void TemplateURLTableModel::OnTemplateURLServiceChanged() {
    381   Reload();
    382 }
    383 
    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 
    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