blob: 0167f1d2560b6f4300430b5ab656be566136a2b1 [file] [log] [blame]
// 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 "base/bind.h"
#include "base/bind_helpers.h"
#include "base/i18n/rtl.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/cancelable_task_tracker.h"
#include "chrome/browser/favicon/favicon_service.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_service.h"
#include "components/favicon_base/favicon_types.h"
#include "grit/generated_resources.h"
#include "grit/ui_resources.h"
#include "third_party/skia/include/core/SkBitmap.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/favicon_size.h"
#include "ui/gfx/image/image_skia.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 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;
FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
model_->template_url_service()->profile(), Profile::EXPLICIT_ACCESS);
if (!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;
favicon_service->GetFaviconImage(
favicon_url,
favicon_base::FAVICON,
gfx::kFaviconSize,
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)
: observer_(NULL),
template_url_service_(template_url_service) {
DCHECK(template_url_service);
template_url_service_->Load();
template_url_service_->AddObserver(this);
Reload();
}
TemplateURLTableModel::~TemplateURLTableModel() {
template_url_service_->RemoveObserver(this);
STLDeleteElements(&entries_);
entries_.clear();
}
void TemplateURLTableModel::Reload() {
STLDeleteElements(&entries_);
entries_.clear();
TemplateURLService::TemplateURLVector urls =
template_url_service_->GetTemplateURLs();
std::vector<ModelEntry*> 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<int>(default_entries.size());
last_other_engine_index_ = last_search_engine_index_ +
static_cast<int>(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<int>(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<ModelEntry> 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.short_name = short_name;
data.SetKeyword(keyword);
data.SetURL(url);
TemplateURL* turl = new TemplateURL(data);
template_url_service_->Add(turl);
scoped_ptr<ModelEntry> entry(new ModelEntry(this, turl));
template_url_service_->AddObserver(this);
AddEntry(index, entry.Pass());
}
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<ModelEntry*>::iterator i = entries_.begin();
i != entries_.end(); ++i) {
ModelEntry* entry = *i;
if (entry->template_url() == template_url)
return static_cast<int>(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<ModelEntry> current_entry(RemoveEntry(index));
const int new_index = last_search_engine_index_++;
AddEntry(new_index, current_entry.Pass());
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<ModelEntry*>::iterator i =
std::find(entries_.begin(), entries_.end(), entry);
DCHECK(i != entries_.end());
NotifyChanged(static_cast<int>(i - entries_.begin()));
}
void TemplateURLTableModel::OnTemplateURLServiceChanged() {
Reload();
}
scoped_ptr<ModelEntry> TemplateURLTableModel::RemoveEntry(int index) {
scoped_ptr<ModelEntry> 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.Pass();
}
void TemplateURLTableModel::AddEntry(int index, scoped_ptr<ModelEntry> entry) {
entries_.insert(entries_.begin() + index, entry.release());
if (index <= last_other_engine_index_)
++last_other_engine_index_;
if (observer_)
observer_->OnItemsAdded(index, 1);
}