1 // Copyright (c) 2011 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/callback.h" 8 #include "base/i18n/rtl.h" 9 #include "base/stl_util-inl.h" 10 #include "base/utf_string_conversions.h" 11 #include "chrome/browser/favicon_service.h" 12 #include "chrome/browser/profiles/profile.h" 13 #include "chrome/browser/search_engines/template_url.h" 14 #include "chrome/browser/search_engines/template_url_model.h" 15 #include "grit/app_resources.h" 16 #include "grit/generated_resources.h" 17 #include "third_party/skia/include/core/SkBitmap.h" 18 #include "ui/base/l10n/l10n_util.h" 19 #include "ui/base/models/table_model_observer.h" 20 #include "ui/base/resource/resource_bundle.h" 21 #include "ui/gfx/codec/png_codec.h" 22 23 // Group IDs used by TemplateURLTableModel. 24 static const int kMainGroupID = 0; 25 static const int kOtherGroupID = 1; 26 27 // ModelEntry ---------------------------------------------------- 28 29 // ModelEntry wraps a TemplateURL as returned from the TemplateURL. 30 // ModelEntry also tracks state information about the URL. 31 32 // Icon used while loading, or if a specific favicon can't be found. 33 static SkBitmap* default_icon = NULL; 34 35 class ModelEntry { 36 public: 37 explicit ModelEntry(TemplateURLTableModel* model, 38 const TemplateURL& template_url) 39 : template_url_(template_url), 40 load_state_(NOT_LOADED), 41 model_(model) { 42 if (!default_icon) { 43 default_icon = ResourceBundle::GetSharedInstance(). 44 GetBitmapNamed(IDR_DEFAULT_FAVICON); 45 } 46 } 47 48 const TemplateURL& template_url() { 49 return template_url_; 50 } 51 52 SkBitmap GetIcon() { 53 if (load_state_ == NOT_LOADED) 54 LoadFavicon(); 55 if (!favicon_.isNull()) 56 return favicon_; 57 return *default_icon; 58 } 59 60 // Resets internal status so that the next time the icon is asked for its 61 // fetched again. This should be invoked if the url is modified. 62 void ResetIcon() { 63 load_state_ = NOT_LOADED; 64 favicon_ = SkBitmap(); 65 } 66 67 private: 68 // State of the favicon. 69 enum LoadState { 70 NOT_LOADED, 71 LOADING, 72 LOADED 73 }; 74 75 void LoadFavicon() { 76 load_state_ = LOADED; 77 FaviconService* favicon_service = 78 model_->template_url_model()->profile()->GetFaviconService( 79 Profile::EXPLICIT_ACCESS); 80 if (!favicon_service) 81 return; 82 GURL favicon_url = template_url().GetFaviconURL(); 83 if (!favicon_url.is_valid()) { 84 // The favicon url isn't always set. Guess at one here. 85 if (template_url_.url() && template_url_.url()->IsValid()) { 86 GURL url = GURL(template_url_.url()->url()); 87 if (url.is_valid()) 88 favicon_url = TemplateURL::GenerateFaviconURL(url); 89 } 90 if (!favicon_url.is_valid()) 91 return; 92 } 93 load_state_ = LOADING; 94 favicon_service->GetFavicon(favicon_url, history::FAVICON, 95 &request_consumer_, 96 NewCallback(this, &ModelEntry::OnFaviconDataAvailable)); 97 } 98 99 void OnFaviconDataAvailable( 100 FaviconService::Handle handle, 101 history::FaviconData favicon) { 102 load_state_ = LOADED; 103 if (favicon.is_valid() && gfx::PNGCodec::Decode(favicon.image_data->front(), 104 favicon.image_data->size(), 105 &favicon_)) { 106 model_->FaviconAvailable(this); 107 } 108 } 109 110 const TemplateURL& template_url_; 111 SkBitmap favicon_; 112 LoadState load_state_; 113 TemplateURLTableModel* model_; 114 CancelableRequestConsumer request_consumer_; 115 116 DISALLOW_COPY_AND_ASSIGN(ModelEntry); 117 }; 118 119 // TemplateURLTableModel ----------------------------------------- 120 121 TemplateURLTableModel::TemplateURLTableModel( 122 TemplateURLModel* template_url_model) 123 : observer_(NULL), 124 template_url_model_(template_url_model) { 125 DCHECK(template_url_model); 126 template_url_model_->Load(); 127 template_url_model_->AddObserver(this); 128 Reload(); 129 } 130 131 TemplateURLTableModel::~TemplateURLTableModel() { 132 template_url_model_->RemoveObserver(this); 133 STLDeleteElements(&entries_); 134 entries_.clear(); 135 } 136 137 void TemplateURLTableModel::Reload() { 138 STLDeleteElements(&entries_); 139 entries_.clear(); 140 141 std::vector<const TemplateURL*> urls = template_url_model_->GetTemplateURLs(); 142 143 // Keywords that can be made the default first. 144 for (std::vector<const TemplateURL*>::iterator i = urls.begin(); 145 i != urls.end(); ++i) { 146 const TemplateURL& template_url = *(*i); 147 // NOTE: we don't use ShowInDefaultList here to avoid items bouncing around 148 // the lists while editing. 149 if (template_url.show_in_default_list()) 150 entries_.push_back(new ModelEntry(this, template_url)); 151 } 152 153 last_search_engine_index_ = static_cast<int>(entries_.size()); 154 155 // Then the rest. 156 for (std::vector<const TemplateURL*>::iterator i = urls.begin(); 157 i != urls.end(); ++i) { 158 const TemplateURL* template_url = *i; 159 // NOTE: we don't use ShowInDefaultList here to avoid things bouncing 160 // the lists while editing. 161 if (!template_url->show_in_default_list() && 162 !template_url->IsExtensionKeyword()) { 163 entries_.push_back(new ModelEntry(this, *template_url)); 164 } 165 } 166 167 if (observer_) 168 observer_->OnModelChanged(); 169 } 170 171 int TemplateURLTableModel::RowCount() { 172 return static_cast<int>(entries_.size()); 173 } 174 175 string16 TemplateURLTableModel::GetText(int row, int col_id) { 176 DCHECK(row >= 0 && row < RowCount()); 177 const TemplateURL& url = entries_[row]->template_url(); 178 if (col_id == IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN) { 179 string16 url_short_name = url.short_name(); 180 // TODO(xji): Consider adding a special case if the short name is a URL, 181 // since those should always be displayed LTR. Please refer to 182 // http://crbug.com/6726 for more information. 183 base::i18n::AdjustStringForLocaleDirection(&url_short_name); 184 if (template_url_model_->GetDefaultSearchProvider() == &url) { 185 return l10n_util::GetStringFUTF16( 186 IDS_SEARCH_ENGINES_EDITOR_DEFAULT_ENGINE, 187 url_short_name); 188 } 189 return url_short_name; 190 } else if (col_id == IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN) { 191 // Keyword should be domain name. Force it to have LTR directionality. 192 string16 keyword = url.keyword(); 193 keyword = base::i18n::GetDisplayStringInLTRDirectionality(keyword); 194 return keyword; 195 } else { 196 NOTREACHED(); 197 return string16(); 198 } 199 } 200 201 SkBitmap TemplateURLTableModel::GetIcon(int row) { 202 DCHECK(row >= 0 && row < RowCount()); 203 return entries_[row]->GetIcon(); 204 } 205 206 void TemplateURLTableModel::SetObserver(ui::TableModelObserver* observer) { 207 observer_ = observer; 208 } 209 210 bool TemplateURLTableModel::HasGroups() { 211 return true; 212 } 213 214 TemplateURLTableModel::Groups TemplateURLTableModel::GetGroups() { 215 Groups groups; 216 217 Group search_engine_group; 218 search_engine_group.title = 219 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAIN_SEPARATOR); 220 search_engine_group.id = kMainGroupID; 221 groups.push_back(search_engine_group); 222 223 Group other_group; 224 other_group.title = 225 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR); 226 other_group.id = kOtherGroupID; 227 groups.push_back(other_group); 228 229 return groups; 230 } 231 232 int TemplateURLTableModel::GetGroupID(int row) { 233 DCHECK(row >= 0 && row < RowCount()); 234 return row < last_search_engine_index_ ? kMainGroupID : kOtherGroupID; 235 } 236 237 void TemplateURLTableModel::Remove(int index) { 238 // Remove the observer while we modify the model, that way we don't need to 239 // worry about the model calling us back when we mutate it. 240 template_url_model_->RemoveObserver(this); 241 const TemplateURL* template_url = &GetTemplateURL(index); 242 243 scoped_ptr<ModelEntry> entry(entries_[index]); 244 entries_.erase(entries_.begin() + index); 245 if (index < last_search_engine_index_) 246 last_search_engine_index_--; 247 if (observer_) 248 observer_->OnItemsRemoved(index, 1); 249 250 // Make sure to remove from the table model first, otherwise the 251 // TemplateURL would be freed. 252 template_url_model_->Remove(template_url); 253 template_url_model_->AddObserver(this); 254 } 255 256 void TemplateURLTableModel::Add(int index, TemplateURL* template_url) { 257 DCHECK(index >= 0 && index <= RowCount()); 258 ModelEntry* entry = new ModelEntry(this, *template_url); 259 entries_.insert(entries_.begin() + index, entry); 260 if (observer_) 261 observer_->OnItemsAdded(index, 1); 262 template_url_model_->RemoveObserver(this); 263 template_url_model_->Add(template_url); 264 template_url_model_->AddObserver(this); 265 } 266 267 void TemplateURLTableModel::ModifyTemplateURL(int index, 268 const string16& title, 269 const string16& keyword, 270 const std::string& url) { 271 DCHECK(index >= 0 && index <= RowCount()); 272 const TemplateURL* template_url = &GetTemplateURL(index); 273 template_url_model_->RemoveObserver(this); 274 template_url_model_->ResetTemplateURL(template_url, title, keyword, url); 275 if (template_url_model_->GetDefaultSearchProvider() == template_url && 276 !TemplateURL::SupportsReplacement(template_url)) { 277 // The entry was the default search provider, but the url has been modified 278 // so that it no longer supports replacement. Reset the default search 279 // provider so that it doesn't point to a bogus entry. 280 template_url_model_->SetDefaultSearchProvider(NULL); 281 } 282 template_url_model_->AddObserver(this); 283 ReloadIcon(index); // Also calls NotifyChanged(). 284 } 285 286 void TemplateURLTableModel::ReloadIcon(int index) { 287 DCHECK(index >= 0 && index < RowCount()); 288 289 entries_[index]->ResetIcon(); 290 291 NotifyChanged(index); 292 } 293 294 const TemplateURL& TemplateURLTableModel::GetTemplateURL(int index) { 295 return entries_[index]->template_url(); 296 } 297 298 int TemplateURLTableModel::IndexOfTemplateURL( 299 const TemplateURL* template_url) { 300 for (std::vector<ModelEntry*>::iterator i = entries_.begin(); 301 i != entries_.end(); ++i) { 302 ModelEntry* entry = *i; 303 if (&(entry->template_url()) == template_url) 304 return static_cast<int>(i - entries_.begin()); 305 } 306 return -1; 307 } 308 309 int TemplateURLTableModel::MoveToMainGroup(int index) { 310 if (index < last_search_engine_index_) 311 return index; // Already in the main group. 312 313 ModelEntry* current_entry = entries_[index]; 314 entries_.erase(index + entries_.begin()); 315 if (observer_) 316 observer_->OnItemsRemoved(index, 1); 317 318 const int new_index = last_search_engine_index_++; 319 entries_.insert(entries_.begin() + new_index, current_entry); 320 if (observer_) 321 observer_->OnItemsAdded(new_index, 1); 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 const TemplateURL* keyword = &GetTemplateURL(index); 332 const TemplateURL* current_default = 333 template_url_model_->GetDefaultSearchProvider(); 334 if (current_default == keyword) 335 return -1; 336 337 template_url_model_->RemoveObserver(this); 338 template_url_model_->SetDefaultSearchProvider(keyword); 339 template_url_model_->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 find(entries_.begin(), entries_.end(), entry); 367 DCHECK(i != entries_.end()); 368 NotifyChanged(static_cast<int>(i - entries_.begin())); 369 } 370 371 void TemplateURLTableModel::OnTemplateURLModelChanged() { 372 Reload(); 373 } 374