back_forward_menu_model.cc revision 3f50c38dc070f4bb515c1b64450dae14f316474e
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 "build/build_config.h" 6 7#include "chrome/browser/ui/toolbar/back_forward_menu_model.h" 8 9#include "app/l10n_util.h" 10#include "app/text_elider.h" 11#include "app/resource_bundle.h" 12#include "base/string_number_conversions.h" 13#include "chrome/browser/metrics/user_metrics.h" 14#include "chrome/browser/tab_contents/navigation_controller.h" 15#include "chrome/browser/tab_contents/navigation_entry.h" 16#include "chrome/browser/tab_contents/tab_contents.h" 17#include "chrome/browser/prefs/pref_service.h" 18#include "chrome/browser/profiles/profile.h" 19#include "chrome/browser/ui/browser.h" 20#include "chrome/common/pref_names.h" 21#include "chrome/common/url_constants.h" 22#include "grit/generated_resources.h" 23#include "grit/theme_resources.h" 24#include "net/base/registry_controlled_domain.h" 25 26const int BackForwardMenuModel::kMaxHistoryItems = 12; 27const int BackForwardMenuModel::kMaxChapterStops = 5; 28static const int kMaxWidth = 700; 29 30BackForwardMenuModel::BackForwardMenuModel(Browser* browser, 31 ModelType model_type) 32 : browser_(browser), 33 test_tab_contents_(NULL), 34 model_type_(model_type) { 35} 36 37bool BackForwardMenuModel::HasIcons() const { 38 return true; 39} 40 41int BackForwardMenuModel::GetItemCount() const { 42 int items = GetHistoryItemCount(); 43 44 if (items > 0) { 45 int chapter_stops = 0; 46 47 // Next, we count ChapterStops, if any. 48 if (items == kMaxHistoryItems) 49 chapter_stops = GetChapterStopCount(items); 50 51 if (chapter_stops) 52 items += chapter_stops + 1; // Chapter stops also need a separator. 53 54 // If the menu is not empty, add two positions in the end 55 // for a separator and a "Show Full History" item. 56 items += 2; 57 } 58 59 return items; 60} 61 62menus::MenuModel::ItemType BackForwardMenuModel::GetTypeAt(int index) const { 63 return IsSeparator(index) ? TYPE_SEPARATOR : TYPE_COMMAND; 64} 65 66int BackForwardMenuModel::GetCommandIdAt(int index) const { 67 return index; 68} 69 70string16 BackForwardMenuModel::GetLabelAt(int index) const { 71 // Return label "Show Full History" for the last item of the menu. 72 if (index == GetItemCount() - 1) 73 return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK); 74 75 // Return an empty string for a separator. 76 if (IsSeparator(index)) 77 return string16(); 78 79 // Return the entry title, escaping any '&' characters and eliding it if it's 80 // super long. 81 NavigationEntry* entry = GetNavigationEntry(index); 82 string16 menu_text(entry->GetTitleForDisplay( 83 GetTabContents()->profile()->GetPrefs()-> 84 GetString(prefs::kAcceptLanguages))); 85 menu_text = gfx::ElideText(menu_text, gfx::Font(), kMaxWidth, false); 86 87 for (size_t i = menu_text.find('&'); i != string16::npos; 88 i = menu_text.find('&', i + 2)) { 89 menu_text.insert(i, 1, '&'); 90 } 91 return menu_text; 92} 93 94bool BackForwardMenuModel::IsItemDynamicAt(int index) const { 95 // This object is only used for a single showing of a menu. 96 return false; 97} 98 99bool BackForwardMenuModel::GetAcceleratorAt( 100 int index, 101 menus::Accelerator* accelerator) const { 102 return false; 103} 104 105bool BackForwardMenuModel::IsItemCheckedAt(int index) const { 106 return false; 107} 108 109int BackForwardMenuModel::GetGroupIdAt(int index) const { 110 return false; 111} 112 113bool BackForwardMenuModel::GetIconAt(int index, SkBitmap* icon) const { 114 if (!ItemHasIcon(index)) 115 return false; 116 117 if (index == GetItemCount() - 1) { 118 *icon = *ResourceBundle::GetSharedInstance().GetBitmapNamed( 119 IDR_HISTORY_FAVICON); 120 } else { 121 NavigationEntry* entry = GetNavigationEntry(index); 122 *icon = entry->favicon().bitmap(); 123 } 124 125 return true; 126} 127 128menus::ButtonMenuItemModel* BackForwardMenuModel::GetButtonMenuItemAt( 129 int index) const { 130 return NULL; 131} 132 133bool BackForwardMenuModel::IsEnabledAt(int index) const { 134 return index < GetItemCount() && !IsSeparator(index); 135} 136 137menus::MenuModel* BackForwardMenuModel::GetSubmenuModelAt(int index) const { 138 return NULL; 139} 140 141void BackForwardMenuModel::HighlightChangedTo(int index) { 142} 143 144void BackForwardMenuModel::ActivatedAt(int index) { 145 ActivatedAtWithDisposition(index, CURRENT_TAB); 146} 147 148void BackForwardMenuModel::ActivatedAtWithDisposition( 149 int index, int disposition) { 150 Profile* profile = browser_->profile(); 151 152 DCHECK(!IsSeparator(index)); 153 154 // Execute the command for the last item: "Show Full History". 155 if (index == GetItemCount() - 1) { 156 UserMetrics::RecordComputedAction(BuildActionName("ShowFullHistory", -1), 157 profile); 158 browser_->ShowSingletonTab(GURL(chrome::kChromeUIHistoryURL), false); 159 return; 160 } 161 162 // Log whether it was a history or chapter click. 163 if (index < GetHistoryItemCount()) { 164 UserMetrics::RecordComputedAction( 165 BuildActionName("HistoryClick", index), profile); 166 } else { 167 UserMetrics::RecordComputedAction( 168 BuildActionName("ChapterClick", index - GetHistoryItemCount() - 1), 169 profile); 170 } 171 172 int controller_index = MenuIndexToNavEntryIndex(index); 173 if (!browser_->NavigateToIndexWithDisposition( 174 controller_index, static_cast<WindowOpenDisposition>(disposition))) { 175 NOTREACHED(); 176 } 177} 178 179void BackForwardMenuModel::MenuWillShow() { 180 UserMetrics::RecordComputedAction(BuildActionName("Popup", -1), 181 browser_->profile()); 182} 183 184bool BackForwardMenuModel::IsSeparator(int index) const { 185 int history_items = GetHistoryItemCount(); 186 // If the index is past the number of history items + separator, 187 // we then consider if it is a chapter-stop entry. 188 if (index > history_items) { 189 // We either are in ChapterStop area, or at the end of the list (the "Show 190 // Full History" link). 191 int chapter_stops = GetChapterStopCount(history_items); 192 if (chapter_stops == 0) 193 return false; // We must have reached the "Show Full History" link. 194 // Otherwise, look to see if we have reached the separator for the 195 // chapter-stops. If not, this is a chapter stop. 196 return (index == history_items + 1 + chapter_stops); 197 } 198 199 // Look to see if we have reached the separator for the history items. 200 return index == history_items; 201} 202 203int BackForwardMenuModel::GetHistoryItemCount() const { 204 TabContents* contents = GetTabContents(); 205 int items = 0; 206 207 if (model_type_ == FORWARD_MENU) { 208 // Only count items from n+1 to end (if n is current entry) 209 items = contents->controller().entry_count() - 210 contents->controller().GetCurrentEntryIndex() - 1; 211 } else { 212 items = contents->controller().GetCurrentEntryIndex(); 213 } 214 215 if (items > kMaxHistoryItems) 216 items = kMaxHistoryItems; 217 else if (items < 0) 218 items = 0; 219 220 return items; 221} 222 223int BackForwardMenuModel::GetChapterStopCount(int history_items) const { 224 TabContents* contents = GetTabContents(); 225 226 int chapter_stops = 0; 227 int current_entry = contents->controller().GetCurrentEntryIndex(); 228 229 if (history_items == kMaxHistoryItems) { 230 int chapter_id = current_entry; 231 if (model_type_ == FORWARD_MENU) { 232 chapter_id += history_items; 233 } else { 234 chapter_id -= history_items; 235 } 236 237 do { 238 chapter_id = GetIndexOfNextChapterStop(chapter_id, 239 model_type_ == FORWARD_MENU); 240 if (chapter_id != -1) 241 ++chapter_stops; 242 } while (chapter_id != -1 && chapter_stops < kMaxChapterStops); 243 } 244 245 return chapter_stops; 246} 247 248int BackForwardMenuModel::GetIndexOfNextChapterStop(int start_from, 249 bool forward) const { 250 TabContents* contents = GetTabContents(); 251 NavigationController& controller = contents->controller(); 252 253 int max_count = controller.entry_count(); 254 if (start_from < 0 || start_from >= max_count) 255 return -1; // Out of bounds. 256 257 if (forward) { 258 if (start_from < max_count - 1) { 259 // We want to advance over the current chapter stop, so we add one. 260 // We don't need to do this when direction is backwards. 261 start_from++; 262 } else { 263 return -1; 264 } 265 } 266 267 NavigationEntry* start_entry = controller.GetEntryAtIndex(start_from); 268 const GURL& url = start_entry->url(); 269 270 if (!forward) { 271 // When going backwards we return the first entry we find that has a 272 // different domain. 273 for (int i = start_from - 1; i >= 0; --i) { 274 if (!net::RegistryControlledDomainService::SameDomainOrHost(url, 275 controller.GetEntryAtIndex(i)->url())) 276 return i; 277 } 278 // We have reached the beginning without finding a chapter stop. 279 return -1; 280 } else { 281 // When going forwards we return the entry before the entry that has a 282 // different domain. 283 for (int i = start_from + 1; i < max_count; ++i) { 284 if (!net::RegistryControlledDomainService::SameDomainOrHost(url, 285 controller.GetEntryAtIndex(i)->url())) 286 return i - 1; 287 } 288 // Last entry is always considered a chapter stop. 289 return max_count - 1; 290 } 291} 292 293int BackForwardMenuModel::FindChapterStop(int offset, 294 bool forward, 295 int skip) const { 296 if (offset < 0 || skip < 0) 297 return -1; 298 299 if (!forward) 300 offset *= -1; 301 302 TabContents* contents = GetTabContents(); 303 int entry = contents->controller().GetCurrentEntryIndex() + offset; 304 for (int i = 0; i < skip + 1; i++) 305 entry = GetIndexOfNextChapterStop(entry, forward); 306 307 return entry; 308} 309 310bool BackForwardMenuModel::ItemHasCommand(int index) const { 311 return index < GetItemCount() && !IsSeparator(index); 312} 313 314bool BackForwardMenuModel::ItemHasIcon(int index) const { 315 return index < GetItemCount() && !IsSeparator(index); 316} 317 318string16 BackForwardMenuModel::GetShowFullHistoryLabel() const { 319 return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK); 320} 321 322TabContents* BackForwardMenuModel::GetTabContents() const { 323 // We use the test tab contents if the unit test has specified it. 324 return test_tab_contents_ ? test_tab_contents_ : 325 browser_->GetSelectedTabContents(); 326} 327 328int BackForwardMenuModel::MenuIndexToNavEntryIndex(int index) const { 329 TabContents* contents = GetTabContents(); 330 int history_items = GetHistoryItemCount(); 331 332 DCHECK_GE(index, 0); 333 334 // Convert anything above the History items separator. 335 if (index < history_items) { 336 if (model_type_ == FORWARD_MENU) { 337 index += contents->controller().GetCurrentEntryIndex() + 1; 338 } else { 339 // Back menu is reverse. 340 index = contents->controller().GetCurrentEntryIndex() - (index + 1); 341 } 342 return index; 343 } 344 if (index == history_items) 345 return -1; // Don't translate the separator for history items. 346 347 if (index >= history_items + 1 + GetChapterStopCount(history_items)) 348 return -1; // This is beyond the last chapter stop so we abort. 349 350 // This menu item is a chapter stop located between the two separators. 351 index = FindChapterStop(history_items, 352 model_type_ == FORWARD_MENU, 353 index - history_items - 1); 354 355 return index; 356} 357 358NavigationEntry* BackForwardMenuModel::GetNavigationEntry(int index) const { 359 int controller_index = MenuIndexToNavEntryIndex(index); 360 NavigationController& controller = GetTabContents()->controller(); 361 if (controller_index >= 0 && controller_index < controller.entry_count()) 362 return controller.GetEntryAtIndex(controller_index); 363 364 NOTREACHED(); 365 return NULL; 366} 367 368std::string BackForwardMenuModel::BuildActionName( 369 const std::string& action, int index) const { 370 DCHECK(!action.empty()); 371 DCHECK(index >= -1); 372 std::string metric_string; 373 if (model_type_ == FORWARD_MENU) 374 metric_string += "ForwardMenu_"; 375 else 376 metric_string += "BackMenu_"; 377 metric_string += action; 378 if (index != -1) { 379 // +1 is for historical reasons (indices used to start at 1). 380 metric_string += base::IntToString(index + 1); 381 } 382 return metric_string; 383} 384