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