Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / custom_home_pages_table_model.cc
blob8b5aab4f0632b571a05277f8f82c56967e8340cc
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/custom_home_pages_table_model.h"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/i18n/rtl.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/history/history_service_factory.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_iterator.h"
16 #include "chrome/browser/ui/browser_list.h"
17 #include "chrome/browser/ui/tabs/tab_strip_model.h"
18 #include "chrome/common/pref_names.h"
19 #include "chrome/common/url_constants.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "components/history/core/browser/history_service.h"
22 #include "content/public/browser/web_contents.h"
23 #include "net/base/net_util.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/base/models/table_model_observer.h"
26 #include "ui/gfx/codec/png_codec.h"
27 #include "url/gurl.h"
29 namespace {
31 // Checks whether the given URL should count as one of the "current" pages.
32 // Returns true for all pages except dev tools and settings.
33 bool ShouldAddPage(const GURL& url) {
34 if (url.is_empty())
35 return false;
37 if (url.SchemeIs(content::kChromeDevToolsScheme))
38 return false;
40 if (url.SchemeIs(content::kChromeUIScheme)) {
41 if (url.host() == chrome::kChromeUISettingsHost)
42 return false;
44 // For a settings page, the path will start with "/settings" not "settings"
45 // so find() will return 1, not 0.
46 if (url.host() == chrome::kChromeUIUberHost &&
47 url.path().find(chrome::kChromeUISettingsHost) == 1) {
48 return false;
52 return true;
55 } // namespace
57 struct CustomHomePagesTableModel::Entry {
58 Entry() : task_id(base::CancelableTaskTracker::kBadTaskId) {}
60 // URL of the page.
61 GURL url;
63 // Page title. If this is empty, we'll display the URL as the entry.
64 base::string16 title;
66 // If not |base::CancelableTaskTracker::kBadTaskId|, indicates we're loading
67 // the title for the page.
68 base::CancelableTaskTracker::TaskId task_id;
71 CustomHomePagesTableModel::CustomHomePagesTableModel(Profile* profile)
72 : profile_(profile),
73 observer_(NULL),
74 num_outstanding_title_lookups_(0) {
77 CustomHomePagesTableModel::~CustomHomePagesTableModel() {
80 void CustomHomePagesTableModel::SetURLs(const std::vector<GURL>& urls) {
81 entries_.resize(urls.size());
82 for (size_t i = 0; i < urls.size(); ++i) {
83 entries_[i].url = urls[i];
84 entries_[i].title.erase();
86 LoadAllTitles();
89 /**
90 * Move a number of existing entries to a new position, reordering the table.
92 * We determine the range of elements affected by the move, save the moved
93 * elements, compact the remaining ones, and re-insert moved elements.
94 * Expects |index_list| to be ordered ascending.
96 void CustomHomePagesTableModel::MoveURLs(int insert_before,
97 const std::vector<int>& index_list) {
98 if (index_list.empty()) return;
99 DCHECK(insert_before >= 0 && insert_before <= RowCount());
101 // The range of elements that needs to be reshuffled is [ |first|, |last| ).
102 int first = std::min(insert_before, index_list.front());
103 int last = std::max(insert_before, index_list.back() + 1);
105 // Save the dragged elements. Also, adjust insertion point if it is before a
106 // dragged element.
107 std::vector<Entry> moved_entries;
108 for (size_t i = 0; i < index_list.size(); ++i) {
109 moved_entries.push_back(entries_[index_list[i]]);
110 if (index_list[i] == insert_before)
111 insert_before++;
114 // Compact the range between beginning and insertion point, moving downwards.
115 size_t skip_count = 0;
116 for (int i = first; i < insert_before; ++i) {
117 if (skip_count < index_list.size() && index_list[skip_count] == i)
118 skip_count++;
119 else
120 entries_[i - skip_count] = entries_[i];
123 // Moving items down created a gap. We start compacting up after it.
124 first = insert_before;
125 insert_before -= skip_count;
127 // Now compact up for elements after the insertion point.
128 skip_count = 0;
129 for (int i = last - 1; i >= first; --i) {
130 if (skip_count < index_list.size() &&
131 index_list[index_list.size() - skip_count - 1] == i) {
132 skip_count++;
133 } else {
134 entries_[i + skip_count] = entries_[i];
138 // Insert moved elements.
139 std::copy(moved_entries.begin(), moved_entries.end(),
140 entries_.begin() + insert_before);
142 // Possibly large change, so tell the view to just rebuild itself.
143 if (observer_)
144 observer_->OnModelChanged();
147 void CustomHomePagesTableModel::AddWithoutNotification(
148 int index, const GURL& url) {
149 DCHECK(index >= 0 && index <= RowCount());
150 entries_.insert(entries_.begin() + static_cast<size_t>(index), Entry());
151 entries_[index].url = url;
154 void CustomHomePagesTableModel::Add(int index, const GURL& url) {
155 AddWithoutNotification(index, url);
156 LoadTitle(&(entries_[index]));
157 if (observer_)
158 observer_->OnItemsAdded(index, 1);
161 void CustomHomePagesTableModel::RemoveWithoutNotification(int index) {
162 DCHECK(index >= 0 && index < RowCount());
163 Entry* entry = &(entries_[index]);
164 // Cancel any pending load requests now so we don't deref a bogus pointer when
165 // we get the loaded notification.
166 if (entry->task_id != base::CancelableTaskTracker::kBadTaskId) {
167 task_tracker_.TryCancel(entry->task_id);
168 entry->task_id = base::CancelableTaskTracker::kBadTaskId;
170 entries_.erase(entries_.begin() + static_cast<size_t>(index));
173 void CustomHomePagesTableModel::Remove(int index) {
174 RemoveWithoutNotification(index);
175 if (observer_)
176 observer_->OnItemsRemoved(index, 1);
179 void CustomHomePagesTableModel::SetToCurrentlyOpenPages() {
180 // Remove the current entries.
181 while (RowCount())
182 RemoveWithoutNotification(0);
184 // And add all tabs for all open browsers with our profile.
185 int add_index = 0;
186 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
187 Browser* browser = *it;
188 if (browser->profile() != profile_)
189 continue; // Skip incognito browsers.
191 for (int tab_index = 0;
192 tab_index < browser->tab_strip_model()->count();
193 ++tab_index) {
194 const GURL url =
195 browser->tab_strip_model()->GetWebContentsAt(tab_index)->GetURL();
196 if (ShouldAddPage(url))
197 AddWithoutNotification(add_index++, url);
200 LoadAllTitles();
203 std::vector<GURL> CustomHomePagesTableModel::GetURLs() {
204 std::vector<GURL> urls(entries_.size());
205 for (size_t i = 0; i < entries_.size(); ++i)
206 urls[i] = entries_[i].url;
207 return urls;
210 int CustomHomePagesTableModel::RowCount() {
211 return static_cast<int>(entries_.size());
214 base::string16 CustomHomePagesTableModel::GetText(int row, int column_id) {
215 DCHECK(column_id == 0);
216 DCHECK(row >= 0 && row < RowCount());
217 return entries_[row].title.empty() ? FormattedURL(row) : entries_[row].title;
220 base::string16 CustomHomePagesTableModel::GetTooltip(int row) {
221 return entries_[row].title.empty() ? base::string16() :
222 l10n_util::GetStringFUTF16(IDS_OPTIONS_STARTUP_PAGE_TOOLTIP,
223 entries_[row].title, FormattedURL(row));
226 void CustomHomePagesTableModel::SetObserver(ui::TableModelObserver* observer) {
227 observer_ = observer;
230 void CustomHomePagesTableModel::LoadTitle(Entry* entry) {
231 history::HistoryService* history_service =
232 HistoryServiceFactory::GetForProfile(profile_,
233 ServiceAccessType::EXPLICIT_ACCESS);
234 if (history_service) {
235 entry->task_id = history_service->QueryURL(
236 entry->url,
237 false,
238 base::Bind(&CustomHomePagesTableModel::OnGotTitle,
239 base::Unretained(this),
240 entry->url,
241 false),
242 &task_tracker_);
246 void CustomHomePagesTableModel::LoadAllTitles() {
247 history::HistoryService* history_service =
248 HistoryServiceFactory::GetForProfile(profile_,
249 ServiceAccessType::EXPLICIT_ACCESS);
250 // It's possible for multiple LoadAllTitles() queries to be inflight we want
251 // to make sure everything is resolved before updating the observer or we risk
252 // getting rendering glitches.
253 num_outstanding_title_lookups_ += entries_.size();
254 for (Entry& entry : entries_) {
255 if (history_service) {
256 entry.task_id = history_service->QueryURL(
257 entry.url,
258 false,
259 base::Bind(&CustomHomePagesTableModel::OnGotOneOfManyTitles,
260 base::Unretained(this),
261 entry.url),
262 &task_tracker_);
265 if (entries_.empty())
266 observer_->OnModelChanged();
269 void CustomHomePagesTableModel::OnGotOneOfManyTitles(const GURL& entry_url,
270 bool found_url,
271 const history::URLRow& row,
272 const history::VisitVector& visits) {
273 OnGotTitle(entry_url, false, found_url, row, visits);
274 DCHECK_GE(num_outstanding_title_lookups_, 1);
275 if (--num_outstanding_title_lookups_ == 0 && observer_)
276 observer_->OnModelChanged();
279 void CustomHomePagesTableModel::OnGotTitle(const GURL& entry_url,
280 bool observable,
281 bool found_url,
282 const history::URLRow& row,
283 const history::VisitVector& visits) {
284 Entry* entry = NULL;
285 size_t entry_index = 0;
286 for (size_t i = 0; i < entries_.size(); ++i) {
287 if (entries_[i].url == entry_url) {
288 entry = &entries_[i];
289 entry_index = i;
290 break;
293 if (!entry) {
294 // The URLs changed before we were called back.
295 return;
297 entry->task_id = base::CancelableTaskTracker::kBadTaskId;
298 if (found_url && !row.title().empty()) {
299 entry->title = row.title();
300 if (observer_ && observable)
301 observer_->OnItemsChanged(static_cast<int>(entry_index), 1);
305 base::string16 CustomHomePagesTableModel::FormattedURL(int row) const {
306 std::string languages =
307 profile_->GetPrefs()->GetString(prefs::kAcceptLanguages);
308 base::string16 url = net::FormatUrl(entries_[row].url, languages);
309 url = base::i18n::GetDisplayStringInLTRDirectionality(url);
310 return url;