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