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/i18n/rtl.h"
9 #include "base/task/cancelable_task_tracker.h"
10 #include "chrome/grit/generated_resources.h"
11 #include "components/favicon/core/favicon_service.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"
21 // Group IDs used by TemplateURLTableModel.
22 static const int kMainGroupID
= 0;
23 static const int kOtherGroupID
= 1;
24 static const int kExtensionGroupID
= 2;
26 // ModelEntry ----------------------------------------------------
28 // ModelEntry wraps a TemplateURL as returned from the TemplateURL.
29 // ModelEntry also tracks state information about the URL.
31 // Icon used while loading, or if a specific favicon can't be found.
32 static const gfx::ImageSkia
* default_icon
= NULL
;
34 class TemplateURLTableModel::ModelEntry
{
36 ModelEntry(TemplateURLTableModel
* model
, TemplateURL
* template_url
)
37 : template_url_(template_url
),
38 load_state_(NOT_LOADED
),
41 default_icon
= ResourceBundle::GetSharedInstance().
42 GetNativeImageNamed(IDR_DEFAULT_FAVICON
).ToImageSkia();
46 TemplateURL
* template_url() {
50 gfx::ImageSkia
GetIcon() {
51 if (load_state_
== NOT_LOADED
)
53 if (!favicon_
.isNull())
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.
61 load_state_
= NOT_LOADED
;
62 favicon_
= gfx::ImageSkia();
66 // State of the favicon.
75 if (!model_
->favicon_service_
)
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());
84 favicon_url
= TemplateURL::GenerateFaviconURL(url
);
86 if (!favicon_url
.is_valid())
89 load_state_
= LOADING
;
90 model_
->favicon_service_
->GetFaviconImage(
92 base::Bind(&ModelEntry::OnFaviconDataAvailable
, base::Unretained(this)),
96 void OnFaviconDataAvailable(
97 const favicon_base::FaviconImageResult
& image_result
) {
99 if (!image_result
.image
.IsEmpty()) {
100 favicon_
= image_result
.image
.AsImageSkia();
101 model_
->FaviconAvailable(this);
105 TemplateURL
* template_url_
;
106 gfx::ImageSkia favicon_
;
107 LoadState load_state_
;
108 TemplateURLTableModel
* model_
;
109 base::CancelableTaskTracker tracker_
;
111 DISALLOW_COPY_AND_ASSIGN(ModelEntry
);
114 // TemplateURLTableModel -----------------------------------------
116 TemplateURLTableModel::TemplateURLTableModel(
117 TemplateURLService
* template_url_service
,
118 favicon::FaviconService
* favicon_service
)
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);
128 TemplateURLTableModel::~TemplateURLTableModel() {
129 template_url_service_
->RemoveObserver(this);
130 STLDeleteElements(&entries_
);
133 void TemplateURLTableModel::Reload() {
134 STLDeleteElements(&entries_
);
136 TemplateURLService::TemplateURLVector urls
=
137 template_url_service_
->GetTemplateURLs();
139 std::vector
<ModelEntry
*> default_entries
, other_entries
, extension_entries
;
140 // Keywords that can be made the default first.
141 for (TemplateURLService::TemplateURLVector::iterator i
= urls
.begin();
142 i
!= urls
.end(); ++i
) {
143 TemplateURL
* template_url
= *i
;
144 // NOTE: we don't use ShowInDefaultList here to avoid items bouncing around
145 // the lists while editing.
146 if (template_url
->show_in_default_list())
147 default_entries
.push_back(new ModelEntry(this, template_url
));
148 else if (template_url
->GetType() == TemplateURL::OMNIBOX_API_EXTENSION
)
149 extension_entries
.push_back(new ModelEntry(this, template_url
));
151 other_entries
.push_back(new ModelEntry(this, template_url
));
154 last_search_engine_index_
= static_cast<int>(default_entries
.size());
155 last_other_engine_index_
= last_search_engine_index_
+
156 static_cast<int>(other_entries
.size());
158 entries_
.insert(entries_
.end(),
159 default_entries
.begin(),
160 default_entries
.end());
162 entries_
.insert(entries_
.end(),
163 other_entries
.begin(),
164 other_entries
.end());
166 entries_
.insert(entries_
.end(),
167 extension_entries
.begin(),
168 extension_entries
.end());
171 observer_
->OnModelChanged();
174 int TemplateURLTableModel::RowCount() {
175 return static_cast<int>(entries_
.size());
178 base::string16
TemplateURLTableModel::GetText(int row
, int col_id
) {
179 DCHECK(row
>= 0 && row
< RowCount());
180 const TemplateURL
* url
= entries_
[row
]->template_url();
181 if (col_id
== IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN
) {
182 base::string16 url_short_name
= url
->short_name();
183 // TODO(xji): Consider adding a special case if the short name is a URL,
184 // since those should always be displayed LTR. Please refer to
185 // http://crbug.com/6726 for more information.
186 base::i18n::AdjustStringForLocaleDirection(&url_short_name
);
187 return (template_url_service_
->GetDefaultSearchProvider() == url
) ?
188 l10n_util::GetStringFUTF16(IDS_SEARCH_ENGINES_EDITOR_DEFAULT_ENGINE
,
189 url_short_name
) : url_short_name
;
192 DCHECK_EQ(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN
, col_id
);
193 // Keyword should be domain name. Force it to have LTR directionality.
194 return base::i18n::GetDisplayStringInLTRDirectionality(url
->keyword());
197 gfx::ImageSkia
TemplateURLTableModel::GetIcon(int row
) {
198 DCHECK(row
>= 0 && row
< RowCount());
199 return entries_
[row
]->GetIcon();
202 void TemplateURLTableModel::SetObserver(ui::TableModelObserver
* observer
) {
203 observer_
= observer
;
206 bool TemplateURLTableModel::HasGroups() {
210 TemplateURLTableModel::Groups
TemplateURLTableModel::GetGroups() {
213 Group search_engine_group
;
214 search_engine_group
.title
=
215 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAIN_SEPARATOR
);
216 search_engine_group
.id
= kMainGroupID
;
217 groups
.push_back(search_engine_group
);
221 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR
);
222 other_group
.id
= kOtherGroupID
;
223 groups
.push_back(other_group
);
225 Group extension_group
;
226 extension_group
.title
=
227 l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_EXTENSIONS_SEPARATOR
);
228 extension_group
.id
= kExtensionGroupID
;
229 groups
.push_back(extension_group
);
234 int TemplateURLTableModel::GetGroupID(int row
) {
235 DCHECK(row
>= 0 && row
< RowCount());
236 if (row
< last_search_engine_index_
)
238 return row
< last_other_engine_index_
? kOtherGroupID
: kExtensionGroupID
;
241 void TemplateURLTableModel::Remove(int index
) {
242 // Remove the observer while we modify the model, that way we don't need to
243 // worry about the model calling us back when we mutate it.
244 template_url_service_
->RemoveObserver(this);
245 TemplateURL
* template_url
= GetTemplateURL(index
);
247 scoped_ptr
<ModelEntry
> entry(RemoveEntry(index
));
249 // Make sure to remove from the table model first, otherwise the
250 // TemplateURL would be freed.
251 template_url_service_
->Remove(template_url
);
252 template_url_service_
->AddObserver(this);
255 void TemplateURLTableModel::Add(int index
,
256 const base::string16
& short_name
,
257 const base::string16
& keyword
,
258 const std::string
& url
) {
259 DCHECK(index
>= 0 && index
<= RowCount());
260 DCHECK(!url
.empty());
261 template_url_service_
->RemoveObserver(this);
262 TemplateURLData data
;
263 data
.SetShortName(short_name
);
264 data
.SetKeyword(keyword
);
266 TemplateURL
* turl
= new TemplateURL(data
);
267 template_url_service_
->Add(turl
);
268 scoped_ptr
<ModelEntry
> entry(new ModelEntry(this, turl
));
269 template_url_service_
->AddObserver(this);
270 AddEntry(index
, entry
.Pass());
273 void TemplateURLTableModel::ModifyTemplateURL(int index
,
274 const base::string16
& title
,
275 const base::string16
& keyword
,
276 const std::string
& url
) {
277 DCHECK(index
>= 0 && index
<= RowCount());
278 DCHECK(!url
.empty());
279 TemplateURL
* template_url
= GetTemplateURL(index
);
280 // The default search provider should support replacement.
281 DCHECK(template_url_service_
->GetDefaultSearchProvider() != template_url
||
282 template_url
->SupportsReplacement(
283 template_url_service_
->search_terms_data()));
284 template_url_service_
->RemoveObserver(this);
285 template_url_service_
->ResetTemplateURL(template_url
, title
, keyword
, url
);
286 template_url_service_
->AddObserver(this);
287 ReloadIcon(index
); // Also calls NotifyChanged().
290 void TemplateURLTableModel::ReloadIcon(int index
) {
291 DCHECK(index
>= 0 && index
< RowCount());
293 entries_
[index
]->ResetIcon();
295 NotifyChanged(index
);
298 TemplateURL
* TemplateURLTableModel::GetTemplateURL(int index
) {
299 return entries_
[index
]->template_url();
302 int TemplateURLTableModel::IndexOfTemplateURL(
303 const TemplateURL
* template_url
) {
304 for (std::vector
<ModelEntry
*>::iterator i
= entries_
.begin();
305 i
!= entries_
.end(); ++i
) {
306 ModelEntry
* entry
= *i
;
307 if (entry
->template_url() == template_url
)
308 return static_cast<int>(i
- entries_
.begin());
313 int TemplateURLTableModel::MoveToMainGroup(int index
) {
314 if (index
< last_search_engine_index_
)
315 return index
; // Already in the main group.
317 scoped_ptr
<ModelEntry
> current_entry(RemoveEntry(index
));
318 const int new_index
= last_search_engine_index_
++;
319 AddEntry(new_index
, current_entry
.Pass());
323 int TemplateURLTableModel::MakeDefaultTemplateURL(int index
) {
324 if (index
< 0 || index
>= RowCount()) {
329 TemplateURL
* keyword
= GetTemplateURL(index
);
330 const TemplateURL
* current_default
=
331 template_url_service_
->GetDefaultSearchProvider();
332 if (current_default
== keyword
)
335 template_url_service_
->RemoveObserver(this);
336 template_url_service_
->SetUserSelectedDefaultSearchProvider(keyword
);
337 template_url_service_
->AddObserver(this);
339 // The formatting of the default engine is different; notify the table that
340 // both old and new entries have changed.
341 if (current_default
!= NULL
) {
342 int old_index
= IndexOfTemplateURL(current_default
);
343 // current_default may not be in the list of TemplateURLs if the database is
344 // corrupt and the default TemplateURL is used from preferences
346 NotifyChanged(old_index
);
348 const int new_index
= IndexOfTemplateURL(keyword
);
349 NotifyChanged(new_index
);
351 // Make sure the new default is in the main group.
352 return MoveToMainGroup(index
);
355 void TemplateURLTableModel::NotifyChanged(int index
) {
358 observer_
->OnItemsChanged(index
, 1);
362 void TemplateURLTableModel::FaviconAvailable(ModelEntry
* entry
) {
363 std::vector
<ModelEntry
*>::iterator i
=
364 std::find(entries_
.begin(), entries_
.end(), entry
);
365 DCHECK(i
!= entries_
.end());
366 NotifyChanged(static_cast<int>(i
- entries_
.begin()));
369 void TemplateURLTableModel::OnTemplateURLServiceChanged() {
373 scoped_ptr
<TemplateURLTableModel::ModelEntry
>
374 TemplateURLTableModel::RemoveEntry(int index
) {
375 scoped_ptr
<ModelEntry
> entry(entries_
[index
]);
376 entries_
.erase(index
+ entries_
.begin());
377 if (index
< last_search_engine_index_
)
378 --last_search_engine_index_
;
379 if (index
< last_other_engine_index_
)
380 --last_other_engine_index_
;
382 observer_
->OnItemsRemoved(index
, 1);
386 void TemplateURLTableModel::AddEntry(int index
, scoped_ptr
<ModelEntry
> entry
) {
387 entries_
.insert(entries_
.begin() + index
, entry
.release());
388 if (index
<= last_other_engine_index_
)
389 ++last_other_engine_index_
;
391 observer_
->OnItemsAdded(index
, 1);