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/custom_home_pages_table_model.h"
6
7#include "base/i18n/rtl.h"
8#include "base/utf_string_conversions.h"
9#include "chrome/browser/prefs/pref_service.h"
10#include "chrome/browser/profiles/profile.h"
11#include "chrome/browser/ui/browser.h"
12#include "chrome/browser/ui/browser_list.h"
13#include "chrome/common/pref_names.h"
14#include "chrome/common/url_constants.h"
15#include "content/browser/tab_contents/tab_contents.h"
16#include "googleurl/src/gurl.h"
17#include "grit/app_resources.h"
18#include "grit/generated_resources.h"
19#include "net/base/net_util.h"
20#include "third_party/skia/include/core/SkBitmap.h"
21#include "ui/base/l10n/l10n_util.h"
22#include "ui/base/models/table_model_observer.h"
23#include "ui/base/resource/resource_bundle.h"
24#include "ui/gfx/codec/png_codec.h"
25
26struct CustomHomePagesTableModel::Entry {
27  Entry() : title_handle(0), favicon_handle(0) {}
28
29  // URL of the page.
30  GURL url;
31
32  // Page title.  If this is empty, we'll display the URL as the entry.
33  string16 title;
34
35  // Icon for the page.
36  SkBitmap icon;
37
38  // If non-zero, indicates we're loading the title for the page.
39  HistoryService::Handle title_handle;
40
41  // If non-zero, indicates we're loading the favicon for the page.
42  FaviconService::Handle favicon_handle;
43};
44
45CustomHomePagesTableModel::CustomHomePagesTableModel(Profile* profile)
46    : default_favicon_(NULL),
47      profile_(profile),
48      observer_(NULL) {
49  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
50  default_favicon_ = rb.GetBitmapNamed(IDR_DEFAULT_FAVICON);
51}
52
53CustomHomePagesTableModel::~CustomHomePagesTableModel() {
54}
55
56void CustomHomePagesTableModel::SetURLs(const std::vector<GURL>& urls) {
57  entries_.resize(urls.size());
58  for (size_t i = 0; i < urls.size(); ++i) {
59    entries_[i].url = urls[i];
60    entries_[i].title.erase();
61    entries_[i].icon.reset();
62    LoadTitleAndFavicon(&(entries_[i]));
63  }
64  // Complete change, so tell the view to just rebuild itself.
65  if (observer_)
66    observer_->OnModelChanged();
67}
68
69void CustomHomePagesTableModel::Add(int index, const GURL& url) {
70  DCHECK(index >= 0 && index <= RowCount());
71  entries_.insert(entries_.begin() + static_cast<size_t>(index), Entry());
72  entries_[index].url = url;
73  LoadTitleAndFavicon(&(entries_[index]));
74  if (observer_)
75    observer_->OnItemsAdded(index, 1);
76}
77
78void CustomHomePagesTableModel::Remove(int index) {
79  DCHECK(index >= 0 && index < RowCount());
80  Entry* entry = &(entries_[index]);
81  // Cancel any pending load requests now so we don't deref a bogus pointer when
82  // we get the loaded notification.
83  if (entry->title_handle) {
84    HistoryService* history_service =
85        profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
86    if (history_service)
87      history_service->CancelRequest(entry->title_handle);
88  }
89  if (entry->favicon_handle) {
90    FaviconService* favicon_service =
91        profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
92    if (favicon_service)
93      favicon_service->CancelRequest(entry->favicon_handle);
94  }
95  entries_.erase(entries_.begin() + static_cast<size_t>(index));
96  if (observer_)
97    observer_->OnItemsRemoved(index, 1);
98}
99
100void CustomHomePagesTableModel::SetToCurrentlyOpenPages() {
101  // Remove the current entries.
102  while (RowCount())
103    Remove(0);
104
105  // And add all tabs for all open browsers with our profile.
106  int add_index = 0;
107  for (BrowserList::const_iterator browser_i = BrowserList::begin();
108       browser_i != BrowserList::end(); ++browser_i) {
109    Browser* browser = *browser_i;
110    if (browser->profile() != profile_)
111      continue;  // Skip incognito browsers.
112
113    for (int tab_index = 0; tab_index < browser->tab_count(); ++tab_index) {
114      const GURL url = browser->GetTabContentsAt(tab_index)->GetURL();
115      if (!url.is_empty() &&
116          !(url.SchemeIs(chrome::kChromeUIScheme) &&
117            url.host() == chrome::kChromeUISettingsHost))
118        Add(add_index++, url);
119    }
120  }
121}
122
123std::vector<GURL> CustomHomePagesTableModel::GetURLs() {
124  std::vector<GURL> urls(entries_.size());
125  for (size_t i = 0; i < entries_.size(); ++i)
126    urls[i] = entries_[i].url;
127  return urls;
128}
129
130int CustomHomePagesTableModel::RowCount() {
131  return static_cast<int>(entries_.size());
132}
133
134string16 CustomHomePagesTableModel::GetText(int row, int column_id) {
135  DCHECK(column_id == 0);
136  DCHECK(row >= 0 && row < RowCount());
137  return entries_[row].title.empty() ? FormattedURL(row) : entries_[row].title;
138}
139
140SkBitmap CustomHomePagesTableModel::GetIcon(int row) {
141  DCHECK(row >= 0 && row < RowCount());
142  return entries_[row].icon.isNull() ? *default_favicon_ : entries_[row].icon;
143}
144
145string16 CustomHomePagesTableModel::GetTooltip(int row) {
146  return entries_[row].title.empty() ? string16() :
147      l10n_util::GetStringFUTF16(IDS_OPTIONS_STARTUP_PAGE_TOOLTIP,
148                                 entries_[row].title, FormattedURL(row));
149}
150
151void CustomHomePagesTableModel::SetObserver(ui::TableModelObserver* observer) {
152  observer_ = observer;
153}
154
155void CustomHomePagesTableModel::LoadTitleAndFavicon(Entry* entry) {
156  HistoryService* history_service =
157      profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
158  if (history_service) {
159    entry->title_handle = history_service->QueryURL(entry->url, false,
160        &query_consumer_,
161        NewCallback(this, &CustomHomePagesTableModel::OnGotTitle));
162  }
163  FaviconService* favicon_service =
164      profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
165  if (favicon_service) {
166    entry->favicon_handle = favicon_service->GetFaviconForURL(entry->url,
167        history::FAVICON, &query_consumer_,
168        NewCallback(this, &CustomHomePagesTableModel::OnGotFavicon));
169  }
170}
171
172void CustomHomePagesTableModel::OnGotTitle(HistoryService::Handle handle,
173                                           bool found_url,
174                                           const history::URLRow* row,
175                                           history::VisitVector* visits) {
176  int entry_index;
177  Entry* entry =
178      GetEntryByLoadHandle(&Entry::title_handle, handle, &entry_index);
179  if (!entry) {
180    // The URLs changed before we were called back.
181    return;
182  }
183  entry->title_handle = 0;
184  if (found_url && !row->title().empty()) {
185    entry->title = row->title();
186    if (observer_)
187      observer_->OnItemsChanged(static_cast<int>(entry_index), 1);
188  }
189}
190
191void CustomHomePagesTableModel::OnGotFavicon(
192    FaviconService::Handle handle,
193    history::FaviconData favicon) {
194  int entry_index;
195  Entry* entry =
196      GetEntryByLoadHandle(&Entry::favicon_handle, handle, &entry_index);
197  if (!entry) {
198    // The URLs changed before we were called back.
199    return;
200  }
201  entry->favicon_handle = 0;
202  if (favicon.is_valid()) {
203    int width, height;
204    std::vector<unsigned char> decoded_data;
205    if (gfx::PNGCodec::Decode(favicon.image_data->front(),
206                              favicon.image_data->size(),
207                              gfx::PNGCodec::FORMAT_BGRA, &decoded_data,
208                              &width, &height)) {
209      entry->icon.setConfig(SkBitmap::kARGB_8888_Config, width, height);
210      entry->icon.allocPixels();
211      memcpy(entry->icon.getPixels(), &decoded_data.front(),
212             width * height * 4);
213      if (observer_)
214        observer_->OnItemsChanged(static_cast<int>(entry_index), 1);
215    }
216  }
217}
218
219CustomHomePagesTableModel::Entry*
220    CustomHomePagesTableModel::GetEntryByLoadHandle(
221    CancelableRequestProvider::Handle Entry::* member,
222    CancelableRequestProvider::Handle handle,
223    int* index) {
224  for (size_t i = 0; i < entries_.size(); ++i) {
225    if (entries_[i].*member == handle) {
226      *index = static_cast<int>(i);
227      return &entries_[i];
228    }
229  }
230  return NULL;
231}
232
233string16 CustomHomePagesTableModel::FormattedURL(int row) const {
234  std::string languages =
235      profile_->GetPrefs()->GetString(prefs::kAcceptLanguages);
236  string16 url = net::FormatUrl(entries_[row].url, languages);
237  url = base::i18n::GetDisplayStringInLTRDirectionality(url);
238  return url;
239}
240