// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/search_engines/template_url_table_model.h" #include #include "base/bind.h" #include "base/i18n/rtl.h" #include "base/macros.h" #include "base/task/cancelable_task_tracker.h" #include "chrome/grit/generated_resources.h" #include "components/favicon/core/favicon_service.h" #include "components/favicon_base/favicon_types.h" #include "components/search_engines/template_url.h" #include "components/search_engines/template_url_service.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/models/table_model_observer.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/image/image_skia.h" #include "ui/resources/grit/ui_resources.h" // Group IDs used by TemplateURLTableModel. static const int kMainGroupID = 0; static const int kOtherGroupID = 1; static const int kExtensionGroupID = 2; // ModelEntry ---------------------------------------------------- // ModelEntry wraps a TemplateURL as returned from the TemplateURL. // ModelEntry also tracks state information about the URL. // Icon used while loading, or if a specific favicon can't be found. static const gfx::ImageSkia* default_icon = NULL; class TemplateURLTableModel::ModelEntry { public: ModelEntry(TemplateURLTableModel* model, TemplateURL* template_url) : template_url_(template_url), load_state_(NOT_LOADED), model_(model) { if (!default_icon) { default_icon = ResourceBundle::GetSharedInstance(). GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToImageSkia(); } } TemplateURL* template_url() { return template_url_; } gfx::ImageSkia GetIcon() { if (load_state_ == NOT_LOADED) LoadFavicon(); if (!favicon_.isNull()) return favicon_; return *default_icon; } // Resets internal status so that the next time the icon is asked for its // fetched again. This should be invoked if the url is modified. void ResetIcon() { load_state_ = NOT_LOADED; favicon_ = gfx::ImageSkia(); } private: // State of the favicon. enum LoadState { NOT_LOADED, LOADING, LOADED }; void LoadFavicon() { load_state_ = LOADED; if (!model_->favicon_service_) return; GURL favicon_url = template_url()->favicon_url(); if (!favicon_url.is_valid()) { // The favicon url isn't always set. Guess at one here. if (template_url_->url_ref().IsValid( model_->template_url_service_->search_terms_data())) { GURL url(template_url_->url()); if (url.is_valid()) favicon_url = TemplateURL::GenerateFaviconURL(url); } if (!favicon_url.is_valid()) return; } load_state_ = LOADING; model_->favicon_service_->GetFaviconImage( favicon_url, base::Bind(&ModelEntry::OnFaviconDataAvailable, base::Unretained(this)), &tracker_); } void OnFaviconDataAvailable( const favicon_base::FaviconImageResult& image_result) { load_state_ = LOADED; if (!image_result.image.IsEmpty()) { favicon_ = image_result.image.AsImageSkia(); model_->FaviconAvailable(this); } } TemplateURL* template_url_; gfx::ImageSkia favicon_; LoadState load_state_; TemplateURLTableModel* model_; base::CancelableTaskTracker tracker_; DISALLOW_COPY_AND_ASSIGN(ModelEntry); }; // TemplateURLTableModel ----------------------------------------- TemplateURLTableModel::TemplateURLTableModel( TemplateURLService* template_url_service, favicon::FaviconService* favicon_service) : observer_(NULL), template_url_service_(template_url_service), favicon_service_(favicon_service) { DCHECK(template_url_service); template_url_service_->Load(); template_url_service_->AddObserver(this); Reload(); } TemplateURLTableModel::~TemplateURLTableModel() { template_url_service_->RemoveObserver(this); STLDeleteElements(&entries_); } void TemplateURLTableModel::Reload() { STLDeleteElements(&entries_); TemplateURLService::TemplateURLVector urls = template_url_service_->GetTemplateURLs(); std::vector default_entries, other_entries, extension_entries; // Keywords that can be made the default first. for (TemplateURLService::TemplateURLVector::iterator i = urls.begin(); i != urls.end(); ++i) { TemplateURL* template_url = *i; // NOTE: we don't use ShowInDefaultList here to avoid items bouncing around // the lists while editing. if (template_url->show_in_default_list()) default_entries.push_back(new ModelEntry(this, template_url)); else if (template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION) extension_entries.push_back(new ModelEntry(this, template_url)); else other_entries.push_back(new ModelEntry(this, template_url)); } last_search_engine_index_ = static_cast(default_entries.size()); last_other_engine_index_ = last_search_engine_index_ + static_cast(other_entries.size()); entries_.insert(entries_.end(), default_entries.begin(), default_entries.end()); entries_.insert(entries_.end(), other_entries.begin(), other_entries.end()); entries_.insert(entries_.end(), extension_entries.begin(), extension_entries.end()); if (observer_) observer_->OnModelChanged(); } int TemplateURLTableModel::RowCount() { return static_cast(entries_.size()); } base::string16 TemplateURLTableModel::GetText(int row, int col_id) { DCHECK(row >= 0 && row < RowCount()); const TemplateURL* url = entries_[row]->template_url(); if (col_id == IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN) { base::string16 url_short_name = url->short_name(); // TODO(xji): Consider adding a special case if the short name is a URL, // since those should always be displayed LTR. Please refer to // http://crbug.com/6726 for more information. base::i18n::AdjustStringForLocaleDirection(&url_short_name); return (template_url_service_->GetDefaultSearchProvider() == url) ? l10n_util::GetStringFUTF16(IDS_SEARCH_ENGINES_EDITOR_DEFAULT_ENGINE, url_short_name) : url_short_name; } DCHECK_EQ(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN, col_id); // Keyword should be domain name. Force it to have LTR directionality. return base::i18n::GetDisplayStringInLTRDirectionality(url->keyword()); } gfx::ImageSkia TemplateURLTableModel::GetIcon(int row) { DCHECK(row >= 0 && row < RowCount()); return entries_[row]->GetIcon(); } void TemplateURLTableModel::SetObserver(ui::TableModelObserver* observer) { observer_ = observer; } bool TemplateURLTableModel::HasGroups() { return true; } TemplateURLTableModel::Groups TemplateURLTableModel::GetGroups() { Groups groups; Group search_engine_group; search_engine_group.title = l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAIN_SEPARATOR); search_engine_group.id = kMainGroupID; groups.push_back(search_engine_group); Group other_group; other_group.title = l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR); other_group.id = kOtherGroupID; groups.push_back(other_group); Group extension_group; extension_group.title = l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_EXTENSIONS_SEPARATOR); extension_group.id = kExtensionGroupID; groups.push_back(extension_group); return groups; } int TemplateURLTableModel::GetGroupID(int row) { DCHECK(row >= 0 && row < RowCount()); if (row < last_search_engine_index_) return kMainGroupID; return row < last_other_engine_index_ ? kOtherGroupID : kExtensionGroupID; } void TemplateURLTableModel::Remove(int index) { // Remove the observer while we modify the model, that way we don't need to // worry about the model calling us back when we mutate it. template_url_service_->RemoveObserver(this); TemplateURL* template_url = GetTemplateURL(index); scoped_ptr entry(RemoveEntry(index)); // Make sure to remove from the table model first, otherwise the // TemplateURL would be freed. template_url_service_->Remove(template_url); template_url_service_->AddObserver(this); } void TemplateURLTableModel::Add(int index, const base::string16& short_name, const base::string16& keyword, const std::string& url) { DCHECK(index >= 0 && index <= RowCount()); DCHECK(!url.empty()); template_url_service_->RemoveObserver(this); TemplateURLData data; data.SetShortName(short_name); data.SetKeyword(keyword); data.SetURL(url); TemplateURL* turl = new TemplateURL(data); template_url_service_->Add(turl); scoped_ptr entry(new ModelEntry(this, turl)); template_url_service_->AddObserver(this); AddEntry(index, std::move(entry)); } void TemplateURLTableModel::ModifyTemplateURL(int index, const base::string16& title, const base::string16& keyword, const std::string& url) { DCHECK(index >= 0 && index <= RowCount()); DCHECK(!url.empty()); TemplateURL* template_url = GetTemplateURL(index); // The default search provider should support replacement. DCHECK(template_url_service_->GetDefaultSearchProvider() != template_url || template_url->SupportsReplacement( template_url_service_->search_terms_data())); template_url_service_->RemoveObserver(this); template_url_service_->ResetTemplateURL(template_url, title, keyword, url); template_url_service_->AddObserver(this); ReloadIcon(index); // Also calls NotifyChanged(). } void TemplateURLTableModel::ReloadIcon(int index) { DCHECK(index >= 0 && index < RowCount()); entries_[index]->ResetIcon(); NotifyChanged(index); } TemplateURL* TemplateURLTableModel::GetTemplateURL(int index) { return entries_[index]->template_url(); } int TemplateURLTableModel::IndexOfTemplateURL( const TemplateURL* template_url) { for (std::vector::iterator i = entries_.begin(); i != entries_.end(); ++i) { ModelEntry* entry = *i; if (entry->template_url() == template_url) return static_cast(i - entries_.begin()); } return -1; } int TemplateURLTableModel::MoveToMainGroup(int index) { if (index < last_search_engine_index_) return index; // Already in the main group. scoped_ptr current_entry(RemoveEntry(index)); const int new_index = last_search_engine_index_++; AddEntry(new_index, std::move(current_entry)); return new_index; } int TemplateURLTableModel::MakeDefaultTemplateURL(int index) { if (index < 0 || index >= RowCount()) { NOTREACHED(); return -1; } TemplateURL* keyword = GetTemplateURL(index); const TemplateURL* current_default = template_url_service_->GetDefaultSearchProvider(); if (current_default == keyword) return -1; template_url_service_->RemoveObserver(this); template_url_service_->SetUserSelectedDefaultSearchProvider(keyword); template_url_service_->AddObserver(this); // The formatting of the default engine is different; notify the table that // both old and new entries have changed. if (current_default != NULL) { int old_index = IndexOfTemplateURL(current_default); // current_default may not be in the list of TemplateURLs if the database is // corrupt and the default TemplateURL is used from preferences if (old_index >= 0) NotifyChanged(old_index); } const int new_index = IndexOfTemplateURL(keyword); NotifyChanged(new_index); // Make sure the new default is in the main group. return MoveToMainGroup(index); } void TemplateURLTableModel::NotifyChanged(int index) { if (observer_) { DCHECK_GE(index, 0); observer_->OnItemsChanged(index, 1); } } void TemplateURLTableModel::FaviconAvailable(ModelEntry* entry) { std::vector::iterator i = std::find(entries_.begin(), entries_.end(), entry); DCHECK(i != entries_.end()); NotifyChanged(static_cast(i - entries_.begin())); } void TemplateURLTableModel::OnTemplateURLServiceChanged() { Reload(); } scoped_ptr TemplateURLTableModel::RemoveEntry(int index) { scoped_ptr entry(entries_[index]); entries_.erase(index + entries_.begin()); if (index < last_search_engine_index_) --last_search_engine_index_; if (index < last_other_engine_index_) --last_other_engine_index_; if (observer_) observer_->OnItemsRemoved(index, 1); return entry; } void TemplateURLTableModel::AddEntry(int index, scoped_ptr entry) { entries_.insert(entries_.begin() + index, entry.release()); if (index <= last_other_engine_index_) ++last_other_engine_index_; if (observer_) observer_->OnItemsAdded(index, 1); }