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