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 "chrome/browser/favicon/favicon_service.h"
     13 #include "chrome/browser/favicon/favicon_service_factory.h"
     14 #include "chrome/browser/profiles/profile.h"
     15 #include "chrome/browser/search_engines/template_url.h"
     16 #include "chrome/browser/search_engines/template_url_service.h"
     17 #include "chrome/common/cancelable_task_tracker.h"
     18 #include "chrome/common/favicon/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 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           GetImageSkiaNamed(IDR_DEFAULT_FAVICON);
     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         GURL url(template_url_->url());
     91         if (url.is_valid())
     92           favicon_url = TemplateURL::GenerateFaviconURL(url);
     93       }
     94       if (!favicon_url.is_valid())
     95         return;
     96     }
     97     load_state_ = LOADING;
     98     favicon_service->GetFaviconImage(
     99         favicon_url, chrome::FAVICON, gfx::kFaviconSize,
    100         base::Bind(&ModelEntry::OnFaviconDataAvailable,
    101                    base::Unretained(this)),
    102         &tracker_);
    103   }
    104 
    105   void OnFaviconDataAvailable(const chrome::FaviconImageResult& image_result) {
    106     load_state_ = LOADED;
    107     if (!image_result.image.IsEmpty()) {
    108       favicon_ = image_result.image.AsImageSkia();
    109       model_->FaviconAvailable(this);
    110     }
    111   }
    112 
    113   TemplateURL* template_url_;
    114   gfx::ImageSkia favicon_;
    115   LoadState load_state_;
    116   TemplateURLTableModel* model_;
    117   CancelableTaskTracker tracker_;
    118 
    119   DISALLOW_COPY_AND_ASSIGN(ModelEntry);
    120 };
    121 
    122 // TemplateURLTableModel -----------------------------------------
    123 
    124 TemplateURLTableModel::TemplateURLTableModel(
    125     TemplateURLService* template_url_service)
    126     : observer_(NULL),
    127       template_url_service_(template_url_service) {
    128   DCHECK(template_url_service);
    129   template_url_service_->Load();
    130   template_url_service_->AddObserver(this);
    131   Reload();
    132 }
    133 
    134 TemplateURLTableModel::~TemplateURLTableModel() {
    135   template_url_service_->RemoveObserver(this);
    136   STLDeleteElements(&entries_);
    137   entries_.clear();
    138 }
    139 
    140 void TemplateURLTableModel::Reload() {
    141   STLDeleteElements(&entries_);
    142   entries_.clear();
    143 
    144   TemplateURLService::TemplateURLVector urls =
    145       template_url_service_->GetTemplateURLs();
    146 
    147   std::vector<ModelEntry*> default_entries, other_entries, extension_entries;
    148   // Keywords that can be made the default first.
    149   for (TemplateURLService::TemplateURLVector::iterator i = urls.begin();
    150        i != urls.end(); ++i) {
    151     TemplateURL* template_url = *i;
    152     // NOTE: we don't use ShowInDefaultList here to avoid items bouncing around
    153     // the lists while editing.
    154     if (template_url->show_in_default_list())
    155       default_entries.push_back(new ModelEntry(this, template_url));
    156     else if (template_url->IsExtensionKeyword())
    157       extension_entries.push_back(new ModelEntry(this, template_url));
    158     else
    159       other_entries.push_back(new ModelEntry(this, template_url));
    160   }
    161 
    162   last_search_engine_index_ = static_cast<int>(default_entries.size());
    163   last_other_engine_index_ = last_search_engine_index_ +
    164       static_cast<int>(other_entries.size());
    165 
    166   entries_.insert(entries_.end(),
    167                   default_entries.begin(),
    168                   default_entries.end());
    169 
    170   entries_.insert(entries_.end(),
    171                   other_entries.begin(),
    172                   other_entries.end());
    173 
    174   entries_.insert(entries_.end(),
    175                   extension_entries.begin(),
    176                   extension_entries.end());
    177 
    178   if (observer_)
    179     observer_->OnModelChanged();
    180 }
    181 
    182 int TemplateURLTableModel::RowCount() {
    183   return static_cast<int>(entries_.size());
    184 }
    185 
    186 string16 TemplateURLTableModel::GetText(int row, int col_id) {
    187   DCHECK(row >= 0 && row < RowCount());
    188   const TemplateURL* url = entries_[row]->template_url();
    189   if (col_id == IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN) {
    190     string16 url_short_name = url->short_name();
    191     // TODO(xji): Consider adding a special case if the short name is a URL,
    192     // since those should always be displayed LTR. Please refer to
    193     // http://crbug.com/6726 for more information.
    194     base::i18n::AdjustStringForLocaleDirection(&url_short_name);
    195     return (template_url_service_->GetDefaultSearchProvider() == url) ?
    196         l10n_util::GetStringFUTF16(IDS_SEARCH_ENGINES_EDITOR_DEFAULT_ENGINE,
    197                                    url_short_name) : url_short_name;
    198   }
    199 
    200   DCHECK_EQ(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN, col_id);
    201   // Keyword should be domain name. Force it to have LTR directionality.
    202   return base::i18n::GetDisplayStringInLTRDirectionality(url->keyword());
    203 }
    204 
    205 gfx::ImageSkia TemplateURLTableModel::GetIcon(int row) {
    206   DCHECK(row >= 0 && row < RowCount());
    207   return entries_[row]->GetIcon();
    208 }
    209 
    210 void TemplateURLTableModel::SetObserver(ui::TableModelObserver* observer) {
    211   observer_ = observer;
    212 }
    213 
    214 bool TemplateURLTableModel::HasGroups() {
    215   return true;
    216 }
    217 
    218 TemplateURLTableModel::Groups TemplateURLTableModel::GetGroups() {
    219   Groups groups;
    220 
    221   Group search_engine_group;
    222   search_engine_group.title =
    223       l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAIN_SEPARATOR);
    224   search_engine_group.id = kMainGroupID;
    225   groups.push_back(search_engine_group);
    226 
    227   Group other_group;
    228   other_group.title =
    229       l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR);
    230   other_group.id = kOtherGroupID;
    231   groups.push_back(other_group);
    232 
    233   Group extension_group;
    234   extension_group.title =
    235       l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_EXTENSIONS_SEPARATOR);
    236   extension_group.id = kExtensionGroupID;
    237   groups.push_back(extension_group);
    238 
    239   return groups;
    240 }
    241 
    242 int TemplateURLTableModel::GetGroupID(int row) {
    243   DCHECK(row >= 0 && row < RowCount());
    244   if (row < last_search_engine_index_)
    245     return kMainGroupID;
    246   return row < last_other_engine_index_ ? kOtherGroupID : kExtensionGroupID;
    247 }
    248 
    249 void TemplateURLTableModel::Remove(int index) {
    250   // Remove the observer while we modify the model, that way we don't need to
    251   // worry about the model calling us back when we mutate it.
    252   template_url_service_->RemoveObserver(this);
    253   TemplateURL* template_url = GetTemplateURL(index);
    254 
    255   scoped_ptr<ModelEntry> entry(RemoveEntry(index));
    256 
    257   // Make sure to remove from the table model first, otherwise the
    258   // TemplateURL would be freed.
    259   template_url_service_->Remove(template_url);
    260   template_url_service_->AddObserver(this);
    261 }
    262 
    263 void TemplateURLTableModel::Add(int index,
    264                                 const string16& short_name,
    265                                 const string16& keyword,
    266                                 const std::string& url) {
    267   DCHECK(index >= 0 && index <= RowCount());
    268   DCHECK(!url.empty());
    269   template_url_service_->RemoveObserver(this);
    270   TemplateURLData data;
    271   data.short_name = short_name;
    272   data.SetKeyword(keyword);
    273   data.SetURL(url);
    274   TemplateURL* turl = new TemplateURL(template_url_service_->profile(), data);
    275   template_url_service_->Add(turl);
    276   scoped_ptr<ModelEntry> entry(new ModelEntry(this, turl));
    277   template_url_service_->AddObserver(this);
    278   AddEntry(index, entry.Pass());
    279 }
    280 
    281 void TemplateURLTableModel::ModifyTemplateURL(int index,
    282                                               const string16& title,
    283                                               const string16& keyword,
    284                                               const std::string& url) {
    285   DCHECK(index >= 0 && index <= RowCount());
    286   DCHECK(!url.empty());
    287   TemplateURL* template_url = GetTemplateURL(index);
    288   // The default search provider should support replacement.
    289   DCHECK(template_url_service_->GetDefaultSearchProvider() != template_url ||
    290          template_url->SupportsReplacement());
    291   template_url_service_->RemoveObserver(this);
    292   template_url_service_->ResetTemplateURL(template_url, title, keyword, url);
    293   template_url_service_->AddObserver(this);
    294   ReloadIcon(index);  // Also calls NotifyChanged().
    295 }
    296 
    297 void TemplateURLTableModel::ReloadIcon(int index) {
    298   DCHECK(index >= 0 && index < RowCount());
    299 
    300   entries_[index]->ResetIcon();
    301 
    302   NotifyChanged(index);
    303 }
    304 
    305 TemplateURL* TemplateURLTableModel::GetTemplateURL(int index) {
    306   return entries_[index]->template_url();
    307 }
    308 
    309 int TemplateURLTableModel::IndexOfTemplateURL(
    310     const TemplateURL* template_url) {
    311   for (std::vector<ModelEntry*>::iterator i = entries_.begin();
    312        i != entries_.end(); ++i) {
    313     ModelEntry* entry = *i;
    314     if (entry->template_url() == template_url)
    315       return static_cast<int>(i - entries_.begin());
    316   }
    317   return -1;
    318 }
    319 
    320 int TemplateURLTableModel::MoveToMainGroup(int index) {
    321   if (index < last_search_engine_index_)
    322     return index;  // Already in the main group.
    323 
    324   scoped_ptr<ModelEntry> current_entry(RemoveEntry(index));
    325   const int new_index = last_search_engine_index_++;
    326   AddEntry(new_index, current_entry.Pass());
    327   return new_index;
    328 }
    329 
    330 int TemplateURLTableModel::MakeDefaultTemplateURL(int index) {
    331   if (index < 0 || index >= RowCount()) {
    332     NOTREACHED();
    333     return -1;
    334   }
    335 
    336   TemplateURL* keyword = GetTemplateURL(index);
    337   const TemplateURL* current_default =
    338       template_url_service_->GetDefaultSearchProvider();
    339   if (current_default == keyword)
    340     return -1;
    341 
    342   template_url_service_->RemoveObserver(this);
    343   template_url_service_->SetDefaultSearchProvider(keyword);
    344   template_url_service_->AddObserver(this);
    345 
    346   // The formatting of the default engine is different; notify the table that
    347   // both old and new entries have changed.
    348   if (current_default != NULL) {
    349     int old_index = IndexOfTemplateURL(current_default);
    350     // current_default may not be in the list of TemplateURLs if the database is
    351     // corrupt and the default TemplateURL is used from preferences
    352     if (old_index >= 0)
    353       NotifyChanged(old_index);
    354   }
    355   const int new_index = IndexOfTemplateURL(keyword);
    356   NotifyChanged(new_index);
    357 
    358   // Make sure the new default is in the main group.
    359   return MoveToMainGroup(index);
    360 }
    361 
    362 void TemplateURLTableModel::NotifyChanged(int index) {
    363   if (observer_) {
    364     DCHECK_GE(index, 0);
    365     observer_->OnItemsChanged(index, 1);
    366   }
    367 }
    368 
    369 void TemplateURLTableModel::FaviconAvailable(ModelEntry* entry) {
    370   std::vector<ModelEntry*>::iterator i =
    371       std::find(entries_.begin(), entries_.end(), entry);
    372   DCHECK(i != entries_.end());
    373   NotifyChanged(static_cast<int>(i - entries_.begin()));
    374 }
    375 
    376 void TemplateURLTableModel::OnTemplateURLServiceChanged() {
    377   Reload();
    378 }
    379 
    380 scoped_ptr<ModelEntry> TemplateURLTableModel::RemoveEntry(int index) {
    381   scoped_ptr<ModelEntry> entry(entries_[index]);
    382   entries_.erase(index + entries_.begin());
    383   if (index < last_search_engine_index_)
    384     --last_search_engine_index_;
    385   if (index < last_other_engine_index_)
    386     --last_other_engine_index_;
    387   if (observer_)
    388     observer_->OnItemsRemoved(index, 1);
    389   return entry.Pass();
    390 }
    391 
    392 void TemplateURLTableModel::AddEntry(int index, scoped_ptr<ModelEntry> entry) {
    393   entries_.insert(entries_.begin() + index, entry.release());
    394   if (index <= last_other_engine_index_)
    395     ++last_other_engine_index_;
    396   if (observer_)
    397     observer_->OnItemsAdded(index, 1);
    398 }
    399