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