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.
5 #include "chrome/browser/ui/search_engines/template_url_table_model.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 "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"
28 // Group IDs used by TemplateURLTableModel.
29 static const int kMainGroupID
= 0;
30 static const int kOtherGroupID
= 1;
31 static const int kExtensionGroupID
= 2;
33 // ModelEntry ----------------------------------------------------
35 // ModelEntry wraps a TemplateURL as returned from the TemplateURL.
36 // ModelEntry also tracks state information about the URL.
38 // Icon used while loading, or if a specific favicon can't be found.
39 static gfx::ImageSkia
* default_icon
= NULL
;
43 ModelEntry(TemplateURLTableModel
* model
, TemplateURL
* template_url
)
44 : template_url_(template_url
),
45 load_state_(NOT_LOADED
),
48 default_icon
= ResourceBundle::GetSharedInstance().
49 GetImageSkiaNamed(IDR_DEFAULT_FAVICON
);
53 TemplateURL
* template_url() {
57 gfx::ImageSkia
GetIcon() {
58 if (load_state_
== NOT_LOADED
)
60 if (!favicon_
.isNull())
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.
68 load_state_
= NOT_LOADED
;
69 favicon_
= gfx::ImageSkia();
73 // State of the favicon.
82 FaviconService
* favicon_service
= FaviconServiceFactory::GetForProfile(
83 model_
->template_url_service()->profile(), Profile::EXPLICIT_ACCESS
);
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());
92 favicon_url
= TemplateURL::GenerateFaviconURL(url
);
94 if (!favicon_url
.is_valid())
97 load_state_
= LOADING
;
98 favicon_service
->GetFaviconImage(
99 favicon_url
, chrome::FAVICON
, gfx::kFaviconSize
,
100 base::Bind(&ModelEntry::OnFaviconDataAvailable
,
101 base::Unretained(this)),
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);
113 TemplateURL
* template_url_
;
114 gfx::ImageSkia favicon_
;
115 LoadState load_state_
;
116 TemplateURLTableModel
* model_
;
117 base::CancelableTaskTracker tracker_
;
119 DISALLOW_COPY_AND_ASSIGN(ModelEntry
);
122 // TemplateURLTableModel -----------------------------------------
124 TemplateURLTableModel::TemplateURLTableModel(
125 TemplateURLService
* template_url_service
)
127 template_url_service_(template_url_service
) {
128 DCHECK(template_url_service
);
129 template_url_service_
->Load();
130 template_url_service_
->AddObserver(this);
134 TemplateURLTableModel::~TemplateURLTableModel() {
135 template_url_service_
->RemoveObserver(this);
136 STLDeleteElements(&entries_
);
140 void TemplateURLTableModel::Reload() {
141 STLDeleteElements(&entries_
);
144 TemplateURLService::TemplateURLVector urls
=
145 template_url_service_
->GetTemplateURLs();
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
->GetType() == TemplateURL::OMNIBOX_API_EXTENSION
)
157 extension_entries
.push_back(new ModelEntry(this, template_url
));
159 other_entries
.push_back(new ModelEntry(this, template_url
));
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());
166 entries_
.insert(entries_
.end(),
167 default_entries
.begin(),
168 default_entries
.end());
170 entries_
.insert(entries_
.end(),
171 other_entries
.begin(),
172 other_entries
.end());
174 entries_
.insert(entries_
.end(),
175 extension_entries
.begin(),
176 extension_entries
.end());
179 observer_
->OnModelChanged();
182 int TemplateURLTableModel::RowCount() {
183 return static_cast<int>(entries_
.size());
186 base::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 base::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
;
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());
205 gfx::ImageSkia
TemplateURLTableModel::GetIcon(int row
) {
206 DCHECK(row
>= 0 && row
< RowCount());
207 return entries_
[row
]->GetIcon();
210 void TemplateURLTableModel::SetObserver(ui::TableModelObserver
* observer
) {
211 observer_
= observer
;
214 bool TemplateURLTableModel::HasGroups() {
218 TemplateURLTableModel::Groups
TemplateURLTableModel::GetGroups() {
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
);
229 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR
);
230 other_group
.id
= kOtherGroupID
;
231 groups
.push_back(other_group
);
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
);
242 int TemplateURLTableModel::GetGroupID(int row
) {
243 DCHECK(row
>= 0 && row
< RowCount());
244 if (row
< last_search_engine_index_
)
246 return row
< last_other_engine_index_
? kOtherGroupID
: kExtensionGroupID
;
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
);
255 scoped_ptr
<ModelEntry
> entry(RemoveEntry(index
));
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);
263 void TemplateURLTableModel::Add(int index
,
264 const base::string16
& short_name
,
265 const base::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
);
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());
281 void TemplateURLTableModel::ModifyTemplateURL(int index
,
282 const base::string16
& title
,
283 const base::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().
297 void TemplateURLTableModel::ReloadIcon(int index
) {
298 DCHECK(index
>= 0 && index
< RowCount());
300 entries_
[index
]->ResetIcon();
302 NotifyChanged(index
);
305 TemplateURL
* TemplateURLTableModel::GetTemplateURL(int index
) {
306 return entries_
[index
]->template_url();
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());
320 int TemplateURLTableModel::MoveToMainGroup(int index
) {
321 if (index
< last_search_engine_index_
)
322 return index
; // Already in the main group.
324 scoped_ptr
<ModelEntry
> current_entry(RemoveEntry(index
));
325 const int new_index
= last_search_engine_index_
++;
326 AddEntry(new_index
, current_entry
.Pass());
330 int TemplateURLTableModel::MakeDefaultTemplateURL(int index
) {
331 if (index
< 0 || index
>= RowCount()) {
336 TemplateURL
* keyword
= GetTemplateURL(index
);
337 const TemplateURL
* current_default
=
338 template_url_service_
->GetDefaultSearchProvider();
339 if (current_default
== keyword
)
342 template_url_service_
->RemoveObserver(this);
343 template_url_service_
->SetDefaultSearchProvider(keyword
);
344 template_url_service_
->AddObserver(this);
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
353 NotifyChanged(old_index
);
355 const int new_index
= IndexOfTemplateURL(keyword
);
356 NotifyChanged(new_index
);
358 // Make sure the new default is in the main group.
359 return MoveToMainGroup(index
);
362 void TemplateURLTableModel::NotifyChanged(int index
) {
365 observer_
->OnItemsChanged(index
, 1);
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()));
376 void TemplateURLTableModel::OnTemplateURLServiceChanged() {
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_
;
388 observer_
->OnItemsRemoved(index
, 1);
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_
;
397 observer_
->OnItemsAdded(index
, 1);