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