1// Copyright (c) 2010 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/possible_url_model.h"
6
7#include "base/callback.h"
8#include "base/i18n/rtl.h"
9#include "base/string_util.h"
10#include "base/utf_string_conversions.h"
11#include "chrome/browser/favicon_service.h"
12#include "chrome/browser/prefs/pref_service.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/common/pref_names.h"
15#include "content/browser/cancelable_request.h"
16#include "grit/app_resources.h"
17#include "grit/generated_resources.h"
18#include "third_party/skia/include/core/SkBitmap.h"
19#include "ui/base/models/table_model_observer.h"
20#include "ui/base/resource/resource_bundle.h"
21#include "ui/base/text/text_elider.h"
22#include "ui/gfx/codec/png_codec.h"
23
24using base::Time;
25using base::TimeDelta;
26
27namespace {
28
29// The default favicon.
30SkBitmap* default_favicon = NULL;
31
32// How long we query entry points for.
33const int kPossibleURLTimeScope = 30;
34
35}  // anonymous namespace
36
37// Contains the data needed to show a result.
38struct PossibleURLModel::Result {
39  Result() : index(0) {}
40
41  GURL url;
42  // Index of this Result in results_. This is used as the key into
43  // favicon_map_ to lookup the favicon for the url, as well as the index
44  // into results_ when the favicon is received.
45  size_t index;
46  ui::SortedDisplayURL display_url;
47  std::wstring title;
48};
49
50
51PossibleURLModel::PossibleURLModel()
52    : profile_(NULL),
53      observer_(NULL) {
54  if (!default_favicon) {
55    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
56    default_favicon = rb.GetBitmapNamed(IDR_DEFAULT_FAVICON);
57  }
58}
59
60PossibleURLModel::~PossibleURLModel() {
61}
62
63void PossibleURLModel::Reload(Profile *profile) {
64  profile_ = profile;
65  consumer_.CancelAllRequests();
66  HistoryService* hs =
67      profile->GetHistoryService(Profile::EXPLICIT_ACCESS);
68  if (hs) {
69    history::QueryOptions options;
70    options.end_time = Time::Now();
71    options.begin_time =
72        options.end_time - TimeDelta::FromDays(kPossibleURLTimeScope);
73    options.max_count = 50;
74
75    hs->QueryHistory(string16(), options, &consumer_,
76        NewCallback(this, &PossibleURLModel::OnHistoryQueryComplete));
77  }
78}
79
80void PossibleURLModel::OnHistoryQueryComplete(HistoryService::Handle h,
81                                              history::QueryResults* result) {
82  results_.resize(result->size());
83  std::string languages = profile_ ?
84      profile_->GetPrefs()->GetString(prefs::kAcceptLanguages) : std::string();
85  for (size_t i = 0; i < result->size(); ++i) {
86    results_[i].url = (*result)[i].url();
87    results_[i].index = i;
88    results_[i].display_url =
89        ui::SortedDisplayURL((*result)[i].url(), languages);
90    results_[i].title = UTF16ToWide((*result)[i].title());
91  }
92
93  // The old version of this code would filter out all but the most recent
94  // visit to each host, plus all typed URLs and AUTO_BOOKMARK transitions. I
95  // think this dialog has a lot of work, and I'm not sure those old
96  // conditions are correct (the results look about equal quality for my
97  // history with and without those conditions), so I'm not spending time
98  // re-implementing them here. They used to be implemented in the history
99  // service, but I think they should be implemented here because that was
100  // pretty specific behavior that shouldn't be generally exposed.
101
102  favicon_map_.clear();
103  if (observer_)
104    observer_->OnModelChanged();
105}
106
107int PossibleURLModel::RowCount() {
108  return static_cast<int>(results_.size());
109}
110
111const GURL& PossibleURLModel::GetURL(int row) {
112  if (row < 0 || row >= RowCount()) {
113    NOTREACHED();
114    return GURL::EmptyGURL();
115  }
116  return results_[row].url;
117}
118
119const std::wstring& PossibleURLModel::GetTitle(int row) {
120  if (row < 0 || row >= RowCount()) {
121    NOTREACHED();
122    return EmptyWString();
123  }
124  return results_[row].title;
125}
126
127string16 PossibleURLModel::GetText(int row, int col_id) {
128  if (row < 0 || row >= RowCount()) {
129    NOTREACHED();
130    return string16();
131  }
132
133  if (col_id == IDS_ASI_PAGE_COLUMN) {
134    string16 title = WideToUTF16Hack(GetTitle(row));
135    // TODO(xji): Consider adding a special case if the title text is a URL,
136    // since those should always have LTR directionality. Please refer to
137    // http://crbug.com/6726 for more information.
138    base::i18n::AdjustStringForLocaleDirection(&title);
139    return title;
140  }
141
142  // TODO(brettw): this should probably pass the GURL up so the URL elider
143  // can be used at a higher level when we know the width.
144  string16 url = results_[row].display_url.display_url();
145  return base::i18n::GetDisplayStringInLTRDirectionality(url);
146}
147
148SkBitmap PossibleURLModel::GetIcon(int row) {
149  if (row < 0 || row >= RowCount()) {
150    NOTREACHED();
151    return *default_favicon;
152  }
153
154  Result& result = results_[row];
155  FaviconMap::iterator i = favicon_map_.find(result.index);
156  if (i != favicon_map_.end()) {
157    // We already requested the favicon, return it.
158    if (!i->second.isNull())
159      return i->second;
160  } else if (profile_) {
161    FaviconService* favicon_service =
162        profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
163    if (favicon_service) {
164      CancelableRequestProvider::Handle h =
165          favicon_service->GetFaviconForURL(
166              result.url, history::FAVICON, &consumer_,
167              NewCallback(this, &PossibleURLModel::OnFaviconAvailable));
168      consumer_.SetClientData(favicon_service, h, result.index);
169      // Add an entry to the map so that we don't attempt to request the
170      // favicon again.
171      favicon_map_[result.index] = SkBitmap();
172    }
173  }
174  return *default_favicon;
175}
176
177int PossibleURLModel::CompareValues(int row1, int row2, int column_id) {
178  if (column_id == IDS_ASI_URL_COLUMN) {
179    return results_[row1].display_url.Compare(
180        results_[row2].display_url, GetCollator());
181  }
182  return ui::TableModel::CompareValues(row1, row2, column_id);
183}
184
185void PossibleURLModel::OnFaviconAvailable(
186    FaviconService::Handle h,
187    history::FaviconData favicon) {
188  if (profile_) {
189    FaviconService* favicon_service =
190        profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
191    size_t index = consumer_.GetClientData(favicon_service, h);
192    if (favicon.is_valid()) {
193      // The decoder will leave our bitmap empty on error.
194      gfx::PNGCodec::Decode(favicon.image_data->front(),
195                            favicon.image_data->size(),
196                            &(favicon_map_[index]));
197
198      // Notify the observer.
199      if (!favicon_map_[index].isNull() && observer_)
200        observer_->OnItemsChanged(static_cast<int>(index), 1);
201    }
202  }
203}
204
205void PossibleURLModel::SetObserver(ui::TableModelObserver* observer) {
206  observer_ = observer;
207}
208