blob: e1bc742c2c6f6bdb1c82f80c084da66f4d7dbc19 [file] [log] [blame]
// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "build/build_config.h"
#include "chrome/browser/back_forward_menu_model.h"
#include "app/l10n_util.h"
#include "app/resource_bundle.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/tab_contents/navigation_controller.h"
#include "chrome/browser/tab_contents/navigation_entry.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/url_constants.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "net/base/registry_controlled_domain.h"
const int BackForwardMenuModel::kMaxHistoryItems = 12;
const int BackForwardMenuModel::kMaxChapterStops = 5;
BackForwardMenuModel::BackForwardMenuModel(Browser* browser,
ModelType model_type)
: browser_(browser),
test_tab_contents_(NULL),
model_type_(model_type) {
}
bool BackForwardMenuModel::HasIcons() const {
return true;
}
int BackForwardMenuModel::GetItemCount() const {
int items = GetHistoryItemCount();
if (items > 0) {
int chapter_stops = 0;
// Next, we count ChapterStops, if any.
if (items == kMaxHistoryItems)
chapter_stops = GetChapterStopCount(items);
if (chapter_stops)
items += chapter_stops + 1; // Chapter stops also need a separator.
// If the menu is not empty, add two positions in the end
// for a separator and a "Show Full History" item.
items += 2;
}
return items;
}
menus::MenuModel::ItemType BackForwardMenuModel::GetTypeAt(int index) const {
return IsSeparator(index) ? TYPE_SEPARATOR : TYPE_COMMAND;
}
int BackForwardMenuModel::GetCommandIdAt(int index) const {
return index;
}
string16 BackForwardMenuModel::GetLabelAt(int index) const {
// Return label "Show Full History" for the last item of the menu.
if (index == GetItemCount() - 1)
return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK);
// Return an empty string for a separator.
if (IsSeparator(index))
return string16();
// Return the entry title, escaping any '&' characters.
NavigationEntry* entry = GetNavigationEntry(index);
string16 menu_text(entry->GetTitleForDisplay(
&GetTabContents()->controller()));
for (size_t i = menu_text.find('&'); i != string16::npos;
i = menu_text.find('&', i + 2)) {
menu_text.insert(i, 1, '&');
}
return menu_text;
}
bool BackForwardMenuModel::IsLabelDynamicAt(int index) const {
// This object is only used for a single showing of a menu.
return false;
}
bool BackForwardMenuModel::GetAcceleratorAt(
int index,
menus::Accelerator* accelerator) const {
return false;
}
bool BackForwardMenuModel::IsItemCheckedAt(int index) const {
return false;
}
int BackForwardMenuModel::GetGroupIdAt(int index) const {
return false;
}
bool BackForwardMenuModel::GetIconAt(int index, SkBitmap* icon) const {
if (!ItemHasIcon(index))
return false;
if (index == GetItemCount() - 1) {
*icon = *ResourceBundle::GetSharedInstance().GetBitmapNamed(
IDR_HISTORY_FAVICON);
} else {
NavigationEntry* entry = GetNavigationEntry(index);
*icon = entry->favicon().bitmap();
}
return true;
}
bool BackForwardMenuModel::IsEnabledAt(int index) const {
return index < GetItemCount() && !IsSeparator(index);
}
menus::MenuModel* BackForwardMenuModel::GetSubmenuModelAt(int index) const {
return NULL;
}
void BackForwardMenuModel::HighlightChangedTo(int index) {
}
void BackForwardMenuModel::ActivatedAt(int index) {
ActivatedAtWithDisposition(index, CURRENT_TAB);
}
void BackForwardMenuModel::ActivatedAtWithDisposition(
int index,
WindowOpenDisposition disposition) {
Profile* profile = browser_->profile();
DCHECK(!IsSeparator(index));
// Execute the command for the last item: "Show Full History".
if (index == GetItemCount() - 1) {
UserMetrics::RecordComputedAction(BuildActionName("ShowFullHistory", -1),
profile);
browser_->ShowSingletonTab(GURL(chrome::kChromeUIHistoryURL));
return;
}
// Log whether it was a history or chapter click.
if (index < GetHistoryItemCount()) {
UserMetrics::RecordComputedAction(
BuildActionName("HistoryClick", index), profile);
} else {
UserMetrics::RecordComputedAction(
BuildActionName("ChapterClick", index - GetHistoryItemCount() - 1),
profile);
}
int controller_index = MenuIndexToNavEntryIndex(index);
if (!browser_->NavigateToIndexWithDisposition(controller_index,
disposition)) {
NOTREACHED();
}
}
void BackForwardMenuModel::MenuWillShow() {
UserMetrics::RecordComputedAction(BuildActionName("Popup", -1),
browser_->profile());
}
bool BackForwardMenuModel::IsSeparator(int index) const {
int history_items = GetHistoryItemCount();
// If the index is past the number of history items + separator,
// we then consider if it is a chapter-stop entry.
if (index > history_items) {
// We either are in ChapterStop area, or at the end of the list (the "Show
// Full History" link).
int chapter_stops = GetChapterStopCount(history_items);
if (chapter_stops == 0)
return false; // We must have reached the "Show Full History" link.
// Otherwise, look to see if we have reached the separator for the
// chapter-stops. If not, this is a chapter stop.
return (index == history_items + 1 + chapter_stops);
}
// Look to see if we have reached the separator for the history items.
return index == history_items;
}
int BackForwardMenuModel::GetHistoryItemCount() const {
TabContents* contents = GetTabContents();
int items = 0;
if (model_type_ == FORWARD_MENU) {
// Only count items from n+1 to end (if n is current entry)
items = contents->controller().entry_count() -
contents->controller().GetCurrentEntryIndex() - 1;
} else {
items = contents->controller().GetCurrentEntryIndex();
}
if (items > kMaxHistoryItems)
items = kMaxHistoryItems;
else if (items < 0)
items = 0;
return items;
}
int BackForwardMenuModel::GetChapterStopCount(int history_items) const {
TabContents* contents = GetTabContents();
int chapter_stops = 0;
int current_entry = contents->controller().GetCurrentEntryIndex();
if (history_items == kMaxHistoryItems) {
int chapter_id = current_entry;
if (model_type_ == FORWARD_MENU) {
chapter_id += history_items;
} else {
chapter_id -= history_items;
}
do {
chapter_id = GetIndexOfNextChapterStop(chapter_id,
model_type_ == FORWARD_MENU);
if (chapter_id != -1)
++chapter_stops;
} while (chapter_id != -1 && chapter_stops < kMaxChapterStops);
}
return chapter_stops;
}
int BackForwardMenuModel::GetIndexOfNextChapterStop(int start_from,
bool forward) const {
TabContents* contents = GetTabContents();
NavigationController& controller = contents->controller();
int max_count = controller.entry_count();
if (start_from < 0 || start_from >= max_count)
return -1; // Out of bounds.
if (forward) {
if (start_from < max_count - 1) {
// We want to advance over the current chapter stop, so we add one.
// We don't need to do this when direction is backwards.
start_from++;
} else {
return -1;
}
}
NavigationEntry* start_entry = controller.GetEntryAtIndex(start_from);
const GURL& url = start_entry->url();
if (!forward) {
// When going backwards we return the first entry we find that has a
// different domain.
for (int i = start_from - 1; i >= 0; --i) {
if (!net::RegistryControlledDomainService::SameDomainOrHost(url,
controller.GetEntryAtIndex(i)->url()))
return i;
}
// We have reached the beginning without finding a chapter stop.
return -1;
} else {
// When going forwards we return the entry before the entry that has a
// different domain.
for (int i = start_from + 1; i < max_count; ++i) {
if (!net::RegistryControlledDomainService::SameDomainOrHost(url,
controller.GetEntryAtIndex(i)->url()))
return i - 1;
}
// Last entry is always considered a chapter stop.
return max_count - 1;
}
}
int BackForwardMenuModel::FindChapterStop(int offset,
bool forward,
int skip) const {
if (offset < 0 || skip < 0)
return -1;
if (!forward)
offset *= -1;
TabContents* contents = GetTabContents();
int entry = contents->controller().GetCurrentEntryIndex() + offset;
for (int i = 0; i < skip + 1; i++)
entry = GetIndexOfNextChapterStop(entry, forward);
return entry;
}
bool BackForwardMenuModel::ItemHasCommand(int index) const {
return index < GetItemCount() && !IsSeparator(index);
}
bool BackForwardMenuModel::ItemHasIcon(int index) const {
return index < GetItemCount() && !IsSeparator(index);
}
string16 BackForwardMenuModel::GetShowFullHistoryLabel() const {
return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK);
}
TabContents* BackForwardMenuModel::GetTabContents() const {
// We use the test tab contents if the unit test has specified it.
return test_tab_contents_ ? test_tab_contents_ :
browser_->GetSelectedTabContents();
}
int BackForwardMenuModel::MenuIndexToNavEntryIndex(int index) const {
TabContents* contents = GetTabContents();
int history_items = GetHistoryItemCount();
DCHECK_GE(index, 0);
// Convert anything above the History items separator.
if (index < history_items) {
if (model_type_ == FORWARD_MENU) {
index += contents->controller().GetCurrentEntryIndex() + 1;
} else {
// Back menu is reverse.
index = contents->controller().GetCurrentEntryIndex() - (index + 1);
}
return index;
}
if (index == history_items)
return -1; // Don't translate the separator for history items.
if (index >= history_items + 1 + GetChapterStopCount(history_items))
return -1; // This is beyond the last chapter stop so we abort.
// This menu item is a chapter stop located between the two separators.
index = FindChapterStop(history_items,
model_type_ == FORWARD_MENU,
index - history_items - 1);
return index;
}
NavigationEntry* BackForwardMenuModel::GetNavigationEntry(int index) const {
int controller_index = MenuIndexToNavEntryIndex(index);
NavigationController& controller = GetTabContents()->controller();
if (controller_index >= 0 && controller_index < controller.entry_count())
return controller.GetEntryAtIndex(controller_index);
NOTREACHED();
return NULL;
}
std::string BackForwardMenuModel::BuildActionName(
const std::string& action, int index) const {
DCHECK(!action.empty());
DCHECK(index >= -1);
std::string metric_string;
if (model_type_ == FORWARD_MENU)
metric_string += "ForwardMenu_";
else
metric_string += "BackMenu_";
metric_string += action;
if (index != -1) {
// +1 is for historical reasons (indices used to start at 1).
metric_string += IntToString(index + 1);
}
return metric_string;
}