custom_home_pages_table_model.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/custom_home_pages_table_model.h"
6
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/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 "content/public/browser/web_contents.h"
21#include "googleurl/src/gurl.h"
22#include "grit/generated_resources.h"
23#include "grit/ui_resources.h"
24#include "net/base/net_util.h"
25#include "ui/base/l10n/l10n_util.h"
26#include "ui/base/models/table_model_observer.h"
27#include "ui/gfx/codec/png_codec.h"
28
29namespace {
30
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.
33bool ShouldAddPage(const GURL& url) {
34  if (url.is_empty())
35    return false;
36
37  if (url.SchemeIs(chrome::kChromeDevToolsScheme))
38    return false;
39
40  if (url.SchemeIs(chrome::kChromeUIScheme)) {
41    if (url.host() == chrome::kChromeUISettingsHost)
42      return false;
43
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;
49    }
50  }
51
52  return true;
53}
54
55}  // namespace
56
57struct CustomHomePagesTableModel::Entry {
58  Entry() : title_handle(0) {}
59
60  // URL of the page.
61  GURL url;
62
63  // Page title.  If this is empty, we'll display the URL as the entry.
64  string16 title;
65
66  // If non-zero, indicates we're loading the title for the page.
67  HistoryService::Handle title_handle;
68};
69
70CustomHomePagesTableModel::CustomHomePagesTableModel(Profile* profile)
71    : profile_(profile),
72      observer_(NULL) {
73}
74
75CustomHomePagesTableModel::~CustomHomePagesTableModel() {
76}
77
78void CustomHomePagesTableModel::SetURLs(const std::vector<GURL>& urls) {
79  entries_.resize(urls.size());
80  for (size_t i = 0; i < urls.size(); ++i) {
81    entries_[i].url = urls[i];
82    entries_[i].title.erase();
83    LoadTitle(&(entries_[i]));
84  }
85  // Complete change, so tell the view to just rebuild itself.
86  if (observer_)
87    observer_->OnModelChanged();
88}
89
90/**
91 * Move a number of existing entries to a new position, reordering the table.
92 *
93 * We determine the range of elements affected by the move, save the moved
94 * elements, compact the remaining ones, and re-insert moved elements.
95 * Expects |index_list| to be ordered ascending.
96 */
97void CustomHomePagesTableModel::MoveURLs(int insert_before,
98                                         const std::vector<int>& index_list) {
99  DCHECK(!index_list.empty());
100  DCHECK(insert_before >= 0 && insert_before <= RowCount());
101
102  // The range of elements that needs to be reshuffled is [ |first|, |last| ).
103  int first = std::min(insert_before, index_list.front());
104  int last = std::max(insert_before, index_list.back() + 1);
105
106  // Save the dragged elements. Also, adjust insertion point if it is before a
107  // dragged element.
108  std::vector<Entry> moved_entries;
109  for (size_t i = 0; i < index_list.size(); ++i) {
110    moved_entries.push_back(entries_[index_list[i]]);
111    if (index_list[i] == insert_before)
112      insert_before++;
113  }
114
115  // Compact the range between beginning and insertion point, moving downwards.
116  size_t skip_count = 0;
117  for (int i = first; i < insert_before; ++i) {
118    if (skip_count < index_list.size() && index_list[skip_count] == i)
119      skip_count++;
120    else
121      entries_[i - skip_count] = entries_[i];
122  }
123
124  // Moving items down created a gap. We start compacting up after it.
125  first = insert_before;
126  insert_before -= skip_count;
127
128  // Now compact up for elements after the insertion point.
129  skip_count = 0;
130  for (int i = last - 1; i >= first; --i) {
131    if (skip_count < index_list.size() &&
132        index_list[index_list.size() - skip_count - 1] == i) {
133      skip_count++;
134    } else {
135      entries_[i + skip_count] = entries_[i];
136    }
137  }
138
139  // Insert moved elements.
140  std::copy(moved_entries.begin(), moved_entries.end(),
141      entries_.begin() + insert_before);
142
143  // Possibly large change, so tell the view to just rebuild itself.
144  if (observer_)
145    observer_->OnModelChanged();
146}
147
148void CustomHomePagesTableModel::Add(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;
152  LoadTitle(&(entries_[index]));
153  if (observer_)
154    observer_->OnItemsAdded(index, 1);
155}
156
157void CustomHomePagesTableModel::Remove(int index) {
158  DCHECK(index >= 0 && index < RowCount());
159  Entry* entry = &(entries_[index]);
160  // Cancel any pending load requests now so we don't deref a bogus pointer when
161  // we get the loaded notification.
162  if (entry->title_handle) {
163    HistoryService* history_service = HistoryServiceFactory::GetForProfile(
164        profile_, Profile::EXPLICIT_ACCESS);
165    if (history_service)
166      history_service->CancelRequest(entry->title_handle);
167  }
168  entries_.erase(entries_.begin() + static_cast<size_t>(index));
169  if (observer_)
170    observer_->OnItemsRemoved(index, 1);
171}
172
173void CustomHomePagesTableModel::SetToCurrentlyOpenPages() {
174  // Remove the current entries.
175  while (RowCount())
176    Remove(0);
177
178  // And add all tabs for all open browsers with our profile.
179  int add_index = 0;
180  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
181    Browser* browser = *it;
182    if (browser->profile() != profile_)
183      continue;  // Skip incognito browsers.
184
185    for (int tab_index = 0;
186         tab_index < browser->tab_strip_model()->count();
187         ++tab_index) {
188      const GURL url =
189          browser->tab_strip_model()->GetWebContentsAt(tab_index)->GetURL();
190      if (ShouldAddPage(url))
191        Add(add_index++, url);
192    }
193  }
194}
195
196std::vector<GURL> CustomHomePagesTableModel::GetURLs() {
197  std::vector<GURL> urls(entries_.size());
198  for (size_t i = 0; i < entries_.size(); ++i)
199    urls[i] = entries_[i].url;
200  return urls;
201}
202
203int CustomHomePagesTableModel::RowCount() {
204  return static_cast<int>(entries_.size());
205}
206
207string16 CustomHomePagesTableModel::GetText(int row, int column_id) {
208  DCHECK(column_id == 0);
209  DCHECK(row >= 0 && row < RowCount());
210  return entries_[row].title.empty() ? FormattedURL(row) : entries_[row].title;
211}
212
213string16 CustomHomePagesTableModel::GetTooltip(int row) {
214  return entries_[row].title.empty() ? string16() :
215      l10n_util::GetStringFUTF16(IDS_OPTIONS_STARTUP_PAGE_TOOLTIP,
216                                 entries_[row].title, FormattedURL(row));
217}
218
219void CustomHomePagesTableModel::SetObserver(ui::TableModelObserver* observer) {
220  observer_ = observer;
221}
222
223void CustomHomePagesTableModel::LoadTitle(Entry* entry) {
224    HistoryService* history_service = HistoryServiceFactory::GetForProfile(
225        profile_, Profile::EXPLICIT_ACCESS);
226  if (history_service) {
227    entry->title_handle = history_service->QueryURL(entry->url, false,
228        &history_query_consumer_,
229        base::Bind(&CustomHomePagesTableModel::OnGotTitle,
230                   base::Unretained(this)));
231  }
232}
233
234void CustomHomePagesTableModel::OnGotTitle(HistoryService::Handle handle,
235                                           bool found_url,
236                                           const history::URLRow* row,
237                                           history::VisitVector* visits) {
238  int entry_index;
239  Entry* entry =
240      GetEntryByLoadHandle(&Entry::title_handle, handle, &entry_index);
241  if (!entry) {
242    // The URLs changed before we were called back.
243    return;
244  }
245  entry->title_handle = 0;
246  if (found_url && !row->title().empty()) {
247    entry->title = row->title();
248    if (observer_)
249      observer_->OnItemsChanged(static_cast<int>(entry_index), 1);
250  }
251}
252
253CustomHomePagesTableModel::Entry*
254    CustomHomePagesTableModel::GetEntryByLoadHandle(
255    CancelableRequestProvider::Handle Entry::* member,
256    CancelableRequestProvider::Handle handle,
257    int* index) {
258  for (size_t i = 0; i < entries_.size(); ++i) {
259    if (entries_[i].*member == handle) {
260      *index = static_cast<int>(i);
261      return &entries_[i];
262    }
263  }
264  return NULL;
265}
266
267string16 CustomHomePagesTableModel::FormattedURL(int row) const {
268  std::string languages =
269      profile_->GetPrefs()->GetString(prefs::kAcceptLanguages);
270  string16 url = net::FormatUrl(entries_[row].url, languages);
271  url = base::i18n::GetDisplayStringInLTRDirectionality(url);
272  return url;
273}
274