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