Home | History | Annotate | Download | only in search_engines
      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.
     24 static const int kMainGroupID = 0;
     25 static 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.
     33 static SkBitmap* default_icon = NULL;
     34 
     35 class 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 
    121 TemplateURLTableModel::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 
    131 TemplateURLTableModel::~TemplateURLTableModel() {
    132   template_url_model_->RemoveObserver(this);
    133   STLDeleteElements(&entries_);
    134   entries_.clear();
    135 }
    136 
    137 void 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 
    171 int TemplateURLTableModel::RowCount() {
    172   return static_cast<int>(entries_.size());
    173 }
    174 
    175 string16 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 
    201 SkBitmap TemplateURLTableModel::GetIcon(int row) {
    202   DCHECK(row >= 0 && row < RowCount());
    203   return entries_[row]->GetIcon();
    204 }
    205 
    206 void TemplateURLTableModel::SetObserver(ui::TableModelObserver* observer) {
    207   observer_ = observer;
    208 }
    209 
    210 bool TemplateURLTableModel::HasGroups() {
    211   return true;
    212 }
    213 
    214 TemplateURLTableModel::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 
    232 int TemplateURLTableModel::GetGroupID(int row) {
    233   DCHECK(row >= 0 && row < RowCount());
    234   return row < last_search_engine_index_ ? kMainGroupID : kOtherGroupID;
    235 }
    236 
    237 void 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 
    256 void 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 
    267 void 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 
    286 void TemplateURLTableModel::ReloadIcon(int index) {
    287   DCHECK(index >= 0 && index < RowCount());
    288 
    289   entries_[index]->ResetIcon();
    290 
    291   NotifyChanged(index);
    292 }
    293 
    294 const TemplateURL& TemplateURLTableModel::GetTemplateURL(int index) {
    295   return entries_[index]->template_url();
    296 }
    297 
    298 int 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 
    309 int 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 
    325 int 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 
    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       find(entries_.begin(), entries_.end(), entry);
    367   DCHECK(i != entries_.end());
    368   NotifyChanged(static_cast<int>(i - entries_.begin()));
    369 }
    370 
    371 void TemplateURLTableModel::OnTemplateURLModelChanged() {
    372   Reload();
    373 }
    374