// Copyright (c) 2011 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 "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
#include <algorithm>
#include <limits>
#include <set>
#include <vector>
#include "base/i18n/rtl.h"
#include "base/metrics/histogram.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/bookmarks/bookmark_utils.h"
#include "chrome/browser/browser_shutdown.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/sync_ui_util.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_context_menu.h"
#include "chrome/browser/ui/views/event_utils.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/renderer_host/render_widget_host_view.h"
#include "content/browser/tab_contents/page_navigator.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/common/notification_service.h"
#include "content/common/page_transition_types.h"
#include "grit/app_resources.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/accessibility/accessible_view_state.h"
#include "ui/base/animation/slide_animation.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/text/text_elider.h"
#include "ui/gfx/canvas_skia.h"
#include "views/controls/button/menu_button.h"
#include "views/controls/label.h"
#include "views/controls/menu/menu_item_view.h"
#include "views/drag_utils.h"
#include "views/metrics.h"
#include "views/view_constants.h"
#include "views/widget/tooltip_manager.h"
#include "views/widget/widget.h"
#include "views/window/window.h"
using views::CustomButton;
using views::DropTargetEvent;
using views::MenuButton;
using views::MenuItemView;
using views::View;
// How much we want the bookmark bar to overlap the toolbar when in its
// 'always shown' mode.
static const int kToolbarOverlap = 3;
// Margins around the content.
static const int kDetachedTopMargin = 1; // When attached, we use 0 and let the
// toolbar above serve as the margin.
static const int kBottomMargin = 2;
static const int kLeftMargin = 1;
static const int kRightMargin = 1;
// Preferred height of the bookmarks bar.
static const int kBarHeight = 28;
// Preferred height of the bookmarks bar when only shown on the new tab page.
const int BookmarkBarView::kNewtabBarHeight = 57;
// Padding between buttons.
static const int kButtonPadding = 0;
// Command ids used in the menu allowing the user to choose when we're visible.
static const int kAlwaysShowCommandID = 1;
// Icon to display when one isn't found for the page.
static SkBitmap* kDefaultFavicon = NULL;
// Icon used for folders.
static SkBitmap* kFolderIcon = NULL;
// Offset for where the menu is shown relative to the bottom of the
// BookmarkBarView.
static const int kMenuOffset = 3;
// Color of the drop indicator.
static const SkColor kDropIndicatorColor = SK_ColorBLACK;
// Width of the drop indicator.
static const int kDropIndicatorWidth = 2;
// Distance between the bottom of the bar and the separator.
static const int kSeparatorMargin = 1;
// Width of the separator between the recently bookmarked button and the
// overflow indicator.
static const int kSeparatorWidth = 4;
// Starting x-coordinate of the separator line within a separator.
static const int kSeparatorStartX = 2;
// Left-padding for the instructional text.
static const int kInstructionsPadding = 6;
// Tag for the 'Other bookmarks' button.
static const int kOtherFolderButtonTag = 1;
// Tag for the sync error button.
static const int kSyncErrorButtonTag = 2;
namespace {
// BookmarkButton -------------------------------------------------------------
// Buttons used for the bookmarks on the bookmark bar.
class BookmarkButton : public views::TextButton {
public:
BookmarkButton(views::ButtonListener* listener,
const GURL& url,
const std::wstring& title,
Profile* profile)
: TextButton(listener, title),
url_(url),
profile_(profile) {
show_animation_.reset(new ui::SlideAnimation(this));
if (BookmarkBarView::testing_) {
// For some reason during testing the events generated by animating
// throw off the test. So, don't animate while testing.
show_animation_->Reset(1);
} else {
show_animation_->Show();
}
}
bool GetTooltipText(const gfx::Point& p, std::wstring* tooltip) {
gfx::Point location(p);
ConvertPointToScreen(this, &location);
*tooltip = BookmarkBarView::CreateToolTipForURLAndTitle(location, url_,
text(), profile_);
return !tooltip->empty();
}
virtual bool IsTriggerableEvent(const views::MouseEvent& e) {
return event_utils::IsPossibleDispositionEvent(e);
}
private:
const GURL& url_;
Profile* profile_;
scoped_ptr<ui::SlideAnimation> show_animation_;
DISALLOW_COPY_AND_ASSIGN(BookmarkButton);
};
// BookmarkFolderButton -------------------------------------------------------
// Buttons used for folders on the bookmark bar, including the 'other folders'
// button.
class BookmarkFolderButton : public views::MenuButton {
public:
BookmarkFolderButton(views::ButtonListener* listener,
const std::wstring& title,
views::ViewMenuDelegate* menu_delegate,
bool show_menu_marker)
: MenuButton(listener, title, menu_delegate, show_menu_marker) {
show_animation_.reset(new ui::SlideAnimation(this));
if (BookmarkBarView::testing_) {
// For some reason during testing the events generated by animating
// throw off the test. So, don't animate while testing.
show_animation_->Reset(1);
} else {
show_animation_->Show();
}
}
virtual bool IsTriggerableEvent(const views::MouseEvent& e) {
// Left clicks should show the menu contents and right clicks should show
// the context menu. They should not trigger the opening of underlying urls.
if (e.flags() == ui::EF_LEFT_BUTTON_DOWN ||
e.flags() == ui::EF_RIGHT_BUTTON_DOWN)
return false;
WindowOpenDisposition disposition(
event_utils::DispositionFromEventFlags(e.flags()));
return disposition != CURRENT_TAB;
}
virtual void OnPaint(gfx::Canvas* canvas) {
views::MenuButton::PaintButton(canvas, views::MenuButton::PB_NORMAL);
}
private:
scoped_ptr<ui::SlideAnimation> show_animation_;
DISALLOW_COPY_AND_ASSIGN(BookmarkFolderButton);
};
// OverFlowButton (chevron) --------------------------------------------------
class OverFlowButton : public views::MenuButton {
public:
explicit OverFlowButton(BookmarkBarView* owner)
: MenuButton(NULL, std::wstring(), owner, false),
owner_(owner) {}
virtual bool OnMousePressed(const views::MouseEvent& e) {
owner_->StopThrobbing(true);
return views::MenuButton::OnMousePressed(e);
}
private:
BookmarkBarView* owner_;
DISALLOW_COPY_AND_ASSIGN(OverFlowButton);
};
void RecordAppLaunch(Profile* profile, GURL url) {
DCHECK(profile->GetExtensionService());
if (!profile->GetExtensionService()->IsInstalledApp(url))
return;
UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram,
extension_misc::APP_LAUNCH_BOOKMARK_BAR,
extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
}
} // namespace
// DropInfo -------------------------------------------------------------------
// Tracks drops on the BookmarkBarView.
struct BookmarkBarView::DropInfo {
DropInfo()
: valid(false),
drop_index(-1),
is_menu_showing(false),
drop_on(false),
is_over_overflow(false),
is_over_other(false),
x(0),
y(0),
drag_operation(0) {
}
// Whether the data is valid.
bool valid;
// Index into the model the drop is over. This is relative to the root node.
int drop_index;
// If true, the menu is being shown.
bool is_menu_showing;
// If true, the user is dropping on a node. This is only used for folder
// nodes.
bool drop_on;
// If true, the user is over the overflow button.
bool is_over_overflow;
// If true, the user is over the other button.
bool is_over_other;
// Coordinates of the drag (in terms of the BookmarkBarView).
int x;
int y;
// The current drag operation.
int drag_operation;
// DropData for the drop.
BookmarkNodeData data;
};
// ButtonSeparatorView --------------------------------------------------------
class BookmarkBarView::ButtonSeparatorView : public views::View {
public:
ButtonSeparatorView() {}
virtual ~ButtonSeparatorView() {}
virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
DetachableToolbarView::PaintVerticalDivider(
canvas, kSeparatorStartX, height(), 1,
DetachableToolbarView::kEdgeDividerColor,
DetachableToolbarView::kMiddleDividerColor,
GetThemeProvider()->GetColor(ThemeService::COLOR_TOOLBAR));
}
virtual gfx::Size GetPreferredSize() OVERRIDE {
// We get the full height of the bookmark bar, so that the height returned
// here doesn't matter.
return gfx::Size(kSeparatorWidth, 1);
}
virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_SEPARATOR);
state->role = ui::AccessibilityTypes::ROLE_SEPARATOR;
}
private:
DISALLOW_COPY_AND_ASSIGN(ButtonSeparatorView);
};
// BookmarkBarView ------------------------------------------------------------
// static
const int BookmarkBarView::kMaxButtonWidth = 150;
const int BookmarkBarView::kNewtabHorizontalPadding = 8;
const int BookmarkBarView::kNewtabVerticalPadding = 12;
// static
bool BookmarkBarView::testing_ = false;
// Returns the bitmap to use for starred folders.
static const SkBitmap& GetFolderIcon() {
if (!kFolderIcon) {
kFolderIcon = ResourceBundle::GetSharedInstance().
GetBitmapNamed(IDR_BOOKMARK_BAR_FOLDER);
}
return *kFolderIcon;
}
BookmarkBarView::BookmarkBarView(Profile* profile, Browser* browser)
: profile_(NULL),
page_navigator_(NULL),
model_(NULL),
bookmark_menu_(NULL),
bookmark_drop_menu_(NULL),
other_bookmarked_button_(NULL),
model_changed_listener_(NULL),
show_folder_drop_menu_task_(NULL),
sync_error_button_(NULL),
sync_service_(NULL),
overflow_button_(NULL),
instructions_(NULL),
bookmarks_separator_view_(NULL),
browser_(browser),
infobar_visible_(false),
throbbing_view_(NULL) {
if (profile->GetProfileSyncService()) {
// Obtain a pointer to the profile sync service and add our instance as an
// observer.
sync_service_ = profile->GetProfileSyncService();
sync_service_->AddObserver(this);
}
SetID(VIEW_ID_BOOKMARK_BAR);
Init();
SetProfile(profile);
size_animation_->Reset(IsAlwaysShown() ? 1 : 0);
}
BookmarkBarView::~BookmarkBarView() {
NotifyModelChanged();
if (model_)
model_->RemoveObserver(this);
// It's possible for the menu to outlive us, reset the observer to make sure
// it doesn't have a reference to us.
if (bookmark_menu_)
bookmark_menu_->set_observer(NULL);
StopShowFolderDropMenuTimer();
if (sync_service_)
sync_service_->RemoveObserver(this);
}
void BookmarkBarView::SetProfile(Profile* profile) {
DCHECK(profile);
if (profile_ == profile)
return;
StopThrobbing(true);
// Cancels the current cancelable.
NotifyModelChanged();
registrar_.RemoveAll();
profile_ = profile;
if (model_)
model_->RemoveObserver(this);
// Disable the other bookmarked button, we'll re-enable when the model is
// loaded.
other_bookmarked_button_->SetEnabled(false);
Source<Profile> ns_source(profile_->GetOriginalProfile());
registrar_.Add(this, NotificationType::BOOKMARK_BUBBLE_SHOWN, ns_source);
registrar_.Add(this, NotificationType::BOOKMARK_BUBBLE_HIDDEN, ns_source);
registrar_.Add(this, NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
NotificationService::AllSources());
// Remove any existing bookmark buttons.
while (GetBookmarkButtonCount())
delete GetChildViewAt(0);
model_ = profile_->GetBookmarkModel();
if (model_) {
model_->AddObserver(this);
if (model_->IsLoaded())
Loaded(model_);
// else case: we'll receive notification back from the BookmarkModel when
// done loading, then we'll populate the bar.
}
}
void BookmarkBarView::SetPageNavigator(PageNavigator* navigator) {
page_navigator_ = navigator;
}
gfx::Size BookmarkBarView::GetPreferredSize() {
return LayoutItems(true);
}
gfx::Size BookmarkBarView::GetMinimumSize() {
// The minimum width of the bookmark bar should at least contain the overflow
// button, by which one can access all the Bookmark Bar items, and the "Other
// Bookmarks" folder, along with appropriate margins and button padding.
int width = kLeftMargin;
if (OnNewTabPage()) {
double current_state = 1 - size_animation_->GetCurrentValue();
width += 2 * static_cast<int>(kNewtabHorizontalPadding * current_state);
}
int sync_error_total_width = 0;
gfx::Size sync_error_button_pref = sync_error_button_->GetPreferredSize();
if (sync_ui_util::ShouldShowSyncErrorButton(sync_service_))
sync_error_total_width += kButtonPadding + sync_error_button_pref.width();
gfx::Size other_bookmarked_pref =
other_bookmarked_button_->GetPreferredSize();
gfx::Size overflow_pref = overflow_button_->GetPreferredSize();
gfx::Size bookmarks_separator_pref =
bookmarks_separator_view_->GetPreferredSize();
width += (other_bookmarked_pref.width() + kButtonPadding +
overflow_pref.width() + kButtonPadding +
bookmarks_separator_pref.width() + sync_error_total_width);
return gfx::Size(width, kBarHeight);
}
void BookmarkBarView::Layout() {
LayoutItems(false);
}
void BookmarkBarView::ViewHierarchyChanged(bool is_add,
View* parent,
View* child) {
if (is_add && child == this) {
// We may get inserted into a hierarchy with a profile - this typically
// occurs when the bar's contents get populated fast enough that the
// buttons are created before the bar is attached to a frame.
UpdateColors();
if (height() > 0) {
// We only layout while parented. When we become parented, if our bounds
// haven't changed, OnBoundsChanged() won't get invoked and we won't
// layout. Therefore we always force a layout when added.
Layout();
}
}
}
void BookmarkBarView::PaintChildren(gfx::Canvas* canvas) {
View::PaintChildren(canvas);
if (drop_info_.get() && drop_info_->valid &&
drop_info_->drag_operation != 0 && drop_info_->drop_index != -1 &&
!drop_info_->is_over_overflow && !drop_info_->drop_on) {
int index = drop_info_->drop_index;
DCHECK(index <= GetBookmarkButtonCount());
int x = 0;
int y = 0;
int h = height();
if (index == GetBookmarkButtonCount()) {
if (index == 0) {
x = kLeftMargin;
} else {
x = GetBookmarkButton(index - 1)->x() +
GetBookmarkButton(index - 1)->width();
}
} else {
x = GetBookmarkButton(index)->x();
}
if (GetBookmarkButtonCount() > 0 && GetBookmarkButton(0)->IsVisible()) {
y = GetBookmarkButton(0)->y();
h = GetBookmarkButton(0)->height();
}
// Since the drop indicator is painted directly onto the canvas, we must
// make sure it is painted in the right location if the locale is RTL.
gfx::Rect indicator_bounds(x - kDropIndicatorWidth / 2,
y,
kDropIndicatorWidth,
h);
indicator_bounds.set_x(GetMirroredXForRect(indicator_bounds));
// TODO(sky/glen): make me pretty!
canvas->FillRectInt(kDropIndicatorColor, indicator_bounds.x(),
indicator_bounds.y(), indicator_bounds.width(),
indicator_bounds.height());
}
}
bool BookmarkBarView::GetDropFormats(
int* formats,
std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
if (!model_ || !model_->IsLoaded())
return false;
*formats = ui::OSExchangeData::URL;
custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat());
return true;
}
bool BookmarkBarView::AreDropTypesRequired() {
return true;
}
bool BookmarkBarView::CanDrop(const ui::OSExchangeData& data) {
if (!model_ || !model_->IsLoaded() ||
!profile_->GetPrefs()->GetBoolean(prefs::kEditBookmarksEnabled))
return false;
if (!drop_info_.get())
drop_info_.reset(new DropInfo());
// Only accept drops of 1 node, which is the case for all data dragged from
// bookmark bar and menus.
return drop_info_->data.Read(data) && drop_info_->data.size() == 1;
}
void BookmarkBarView::OnDragEntered(const DropTargetEvent& event) {
}
int BookmarkBarView::OnDragUpdated(const DropTargetEvent& event) {
if (!drop_info_.get())
return 0;
if (drop_info_->valid &&
(drop_info_->x == event.x() && drop_info_->y == event.y())) {
// The location of the mouse didn't change, return the last operation.
return drop_info_->drag_operation;
}
drop_info_->x = event.x();
drop_info_->y = event.y();
int drop_index;
bool drop_on;
bool is_over_overflow;
bool is_over_other;
drop_info_->drag_operation = CalculateDropOperation(
event, drop_info_->data, &drop_index, &drop_on, &is_over_overflow,
&is_over_other);
if (drop_info_->valid && drop_info_->drop_index == drop_index &&
drop_info_->drop_on == drop_on &&
drop_info_->is_over_overflow == is_over_overflow &&
drop_info_->is_over_other == is_over_other) {
// The position we're going to drop didn't change, return the last drag
// operation we calculated.
return drop_info_->drag_operation;
}
drop_info_->valid = true;
StopShowFolderDropMenuTimer();
// TODO(sky): Optimize paint region.
SchedulePaint();
drop_info_->drop_index = drop_index;
drop_info_->drop_on = drop_on;
drop_info_->is_over_overflow = is_over_overflow;
drop_info_->is_over_other = is_over_other;
if (drop_info_->is_menu_showing) {
if (bookmark_drop_menu_)
bookmark_drop_menu_->Cancel();
drop_info_->is_menu_showing = false;
}
if (drop_on || is_over_overflow || is_over_other) {
const BookmarkNode* node;
if (is_over_other)
node = model_->other_node();
else if (is_over_overflow)
node = model_->GetBookmarkBarNode();
else
node = model_->GetBookmarkBarNode()->GetChild(drop_index);
StartShowFolderDropMenuTimer(node);
}
return drop_info_->drag_operation;
}
void BookmarkBarView::OnDragExited() {
StopShowFolderDropMenuTimer();
// NOTE: we don't hide the menu on exit as it's possible the user moved the
// mouse over the menu, which triggers an exit on us.
drop_info_->valid = false;
if (drop_info_->drop_index != -1) {
// TODO(sky): optimize the paint region.
SchedulePaint();
}
drop_info_.reset();
}
int BookmarkBarView::OnPerformDrop(const DropTargetEvent& event) {
StopShowFolderDropMenuTimer();
if (bookmark_drop_menu_)
bookmark_drop_menu_->Cancel();
if (!drop_info_.get() || !drop_info_->drag_operation)
return ui::DragDropTypes::DRAG_NONE;
const BookmarkNode* root =
drop_info_->is_over_other ? model_->other_node() :
model_->GetBookmarkBarNode();
int index = drop_info_->drop_index;
const bool drop_on = drop_info_->drop_on;
const BookmarkNodeData data = drop_info_->data;
const bool is_over_other = drop_info_->is_over_other;
DCHECK(data.is_valid());
if (drop_info_->drop_index != -1) {
// TODO(sky): optimize the SchedulePaint region.
SchedulePaint();
}
drop_info_.reset();
const BookmarkNode* parent_node;
if (is_over_other) {
parent_node = root;
index = parent_node->child_count();
} else if (drop_on) {
parent_node = root->GetChild(index);
index = parent_node->child_count();
} else {
parent_node = root;
}
return bookmark_utils::PerformBookmarkDrop(profile_, data, parent_node,
index);
}
void BookmarkBarView::ShowContextMenu(const gfx::Point& p,
bool is_mouse_gesture) {
ShowContextMenuForView(this, p, is_mouse_gesture);
}
void BookmarkBarView::GetAccessibleState(ui::AccessibleViewState* state) {
state->role = ui::AccessibilityTypes::ROLE_TOOLBAR;
state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_BOOKMARKS);
}
void BookmarkBarView::OnStateChanged() {
// When the sync state changes, it is sufficient to invoke View::Layout since
// during layout we query the profile sync service and determine whether the
// new state requires showing the sync error button so that the user can
// re-enter her password. If extension shelf appears along with the bookmark
// shelf, it too needs to be layed out. Since both have the same parent, it is
// enough to let the parent layout both of these children.
// TODO(sky): This should not require Layout() and SchedulePaint(). Needs
// some cleanup.
PreferredSizeChanged();
Layout();
SchedulePaint();
}
void BookmarkBarView::OnFullscreenToggled(bool fullscreen) {
if (!fullscreen)
size_animation_->Reset(IsAlwaysShown() ? 1 : 0);
else if (IsAlwaysShown())
size_animation_->Reset(0);
}
bool BookmarkBarView::IsDetached() const {
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kNewTabPage4))
return false;
return OnNewTabPage() && (size_animation_->GetCurrentValue() != 1);
}
bool BookmarkBarView::IsOnTop() const {
return true;
}
double BookmarkBarView::GetAnimationValue() const {
return size_animation_->GetCurrentValue();
}
int BookmarkBarView::GetToolbarOverlap() const {
return GetToolbarOverlap(false);
}
bool BookmarkBarView::IsAlwaysShown() const {
return (profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar) &&
profile_->GetPrefs()->GetBoolean(prefs::kEnableBookmarkBar));
}
bool BookmarkBarView::OnNewTabPage() const {
return (browser_ && browser_->GetSelectedTabContents() &&
browser_->GetSelectedTabContents()->ShouldShowBookmarkBar());
}
int BookmarkBarView::GetToolbarOverlap(bool return_max) const {
// When not on the New Tab Page, always overlap by the full amount.
if (return_max || !OnNewTabPage())
return kToolbarOverlap;
// When on the New Tab Page with an infobar, overlap by 0 whenever the infobar
// is above us (i.e. when we're detached), since drawing over the infobar
// looks weird.
if (IsDetached() && infobar_visible_)
return 0;
// When on the New Tab Page with no infobar, animate the overlap between the
// attached and detached states.
return static_cast<int>(kToolbarOverlap * size_animation_->GetCurrentValue());
}
bool BookmarkBarView::is_animating() {
return size_animation_->is_animating();
}
void BookmarkBarView::AnimationProgressed(const ui::Animation* animation) {
if (browser_)
browser_->BookmarkBarSizeChanged(true);
}
void BookmarkBarView::AnimationEnded(const ui::Animation* animation) {
if (browser_)
browser_->BookmarkBarSizeChanged(false);
SchedulePaint();
}
void BookmarkBarView::BookmarkMenuDeleted(BookmarkMenuController* controller) {
if (controller == bookmark_menu_)
bookmark_menu_ = NULL;
else if (controller == bookmark_drop_menu_)
bookmark_drop_menu_ = NULL;
}
views::TextButton* BookmarkBarView::GetBookmarkButton(int index) {
DCHECK(index >= 0 && index < GetBookmarkButtonCount());
return static_cast<views::TextButton*>(GetChildViewAt(index));
}
views::MenuItemView* BookmarkBarView::GetMenu() {
return bookmark_menu_ ? bookmark_menu_->menu() : NULL;
}
views::MenuItemView* BookmarkBarView::GetContextMenu() {
return bookmark_menu_ ? bookmark_menu_->context_menu() : NULL;
}
views::MenuItemView* BookmarkBarView::GetDropMenu() {
return bookmark_drop_menu_ ? bookmark_drop_menu_->menu() : NULL;
}
const BookmarkNode* BookmarkBarView::GetNodeForButtonAt(const gfx::Point& loc,
int* start_index) {
*start_index = 0;
if (loc.x() < 0 || loc.x() >= width() || loc.y() < 0 || loc.y() >= height())
return NULL;
gfx::Point adjusted_loc(GetMirroredXInView(loc.x()), loc.y());
// Check the buttons first.
for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
views::View* child = GetChildViewAt(i);
if (!child->IsVisible())
break;
if (child->bounds().Contains(adjusted_loc))
return model_->GetBookmarkBarNode()->GetChild(i);
}
// Then the overflow button.
if (overflow_button_->IsVisible() &&
overflow_button_->bounds().Contains(adjusted_loc)) {
*start_index = GetFirstHiddenNodeIndex();
return model_->GetBookmarkBarNode();
}
// And finally the other folder.
if (other_bookmarked_button_->IsVisible() &&
other_bookmarked_button_->bounds().Contains(adjusted_loc)) {
return model_->other_node();
}
return NULL;
}
views::MenuButton* BookmarkBarView::GetMenuButtonForNode(
const BookmarkNode* node) {
if (node == model_->other_node())
return other_bookmarked_button_;
if (node == model_->GetBookmarkBarNode())
return overflow_button_;
int index = model_->GetBookmarkBarNode()->GetIndexOf(node);
if (index == -1 || !node->is_folder())
return NULL;
return static_cast<views::MenuButton*>(GetChildViewAt(index));
}
void BookmarkBarView::GetAnchorPositionAndStartIndexForButton(
views::MenuButton* button,
MenuItemView::AnchorPosition* anchor,
int* start_index) {
if (button == other_bookmarked_button_ || button == overflow_button_)
*anchor = MenuItemView::TOPRIGHT;
else
*anchor = MenuItemView::TOPLEFT;
// Invert orientation if right to left.
if (base::i18n::IsRTL()) {
if (*anchor == MenuItemView::TOPRIGHT)
*anchor = MenuItemView::TOPLEFT;
else
*anchor = MenuItemView::TOPRIGHT;
}
if (button == overflow_button_)
*start_index = GetFirstHiddenNodeIndex();
else
*start_index = 0;
}
void BookmarkBarView::ShowImportDialog() {
browser_->OpenImportSettingsDialog();
}
void BookmarkBarView::Init() {
// Note that at this point we're not in a hierarchy so GetThemeProvider() will
// return NULL. When we're inserted into a hierarchy, we'll call
// UpdateColors(), which will set the appropriate colors for all the objects
// added in this function.
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
if (!kDefaultFavicon)
kDefaultFavicon = rb.GetBitmapNamed(IDR_DEFAULT_FAVICON);
// Child views are traversed in the order they are added. Make sure the order
// they are added matches the visual order.
sync_error_button_ = CreateSyncErrorButton();
AddChildView(sync_error_button_);
overflow_button_ = CreateOverflowButton();
AddChildView(overflow_button_);
other_bookmarked_button_ = CreateOtherBookmarkedButton();
AddChildView(other_bookmarked_button_);
bookmarks_separator_view_ = new ButtonSeparatorView();
AddChildView(bookmarks_separator_view_);
instructions_ = new BookmarkBarInstructionsView(this);
AddChildView(instructions_);
SetContextMenuController(this);
size_animation_.reset(new ui::SlideAnimation(this));
}
MenuButton* BookmarkBarView::CreateOtherBookmarkedButton() {
MenuButton* button = new BookmarkFolderButton(
this,
UTF16ToWide(l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OTHER_BOOKMARKED)),
this,
false);
button->SetID(VIEW_ID_OTHER_BOOKMARKS);
button->SetIcon(GetFolderIcon());
button->SetContextMenuController(this);
button->set_tag(kOtherFolderButtonTag);
button->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OTHER_BOOKMARKED));
return button;
}
MenuButton* BookmarkBarView::CreateOverflowButton() {
MenuButton* button = new OverFlowButton(this);
button->SetIcon(*ResourceBundle::GetSharedInstance().
GetBitmapNamed(IDR_BOOKMARK_BAR_CHEVRONS));
// The overflow button's image contains an arrow and therefore it is a
// direction sensitive image and we need to flip it if the UI layout is
// right-to-left.
//
// By default, menu buttons are not flipped because they generally contain
// text and flipping the gfx::Canvas object will break text rendering. Since
// the overflow button does not contain text, we can safely flip it.
button->EnableCanvasFlippingForRTLUI(true);
// Make visible as necessary.
button->SetVisible(false);
// Set accessibility name.
button->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_ACCNAME_BOOKMARKS_CHEVRON));
return button;
}
void BookmarkBarView::Loaded(BookmarkModel* model) {
volatile int button_count = GetBookmarkButtonCount();
DCHECK(button_count == 0); // If non-zero it means Load was invoked more than
// once, or we didn't properly clear things.
// Either of which shouldn't happen
const BookmarkNode* node = model_->GetBookmarkBarNode();
DCHECK(node && model_->other_node());
// Create a button for each of the children on the bookmark bar.
for (int i = 0, child_count = node->child_count(); i < child_count; ++i)
AddChildViewAt(CreateBookmarkButton(node->GetChild(i)), i);
UpdateColors();
UpdateOtherBookmarksVisibility();
other_bookmarked_button_->SetEnabled(true);
Layout();
SchedulePaint();
}
void BookmarkBarView::BookmarkModelBeingDeleted(BookmarkModel* model) {
// In normal shutdown The bookmark model should never be deleted before us.
// When X exits suddenly though, it can happen, This code exists
// to check for regressions in shutdown code and not crash.
if (!browser_shutdown::ShuttingDownWithoutClosingBrowsers())
NOTREACHED();
// Do minimal cleanup, presumably we'll be deleted shortly.
NotifyModelChanged();
model_->RemoveObserver(this);
model_ = NULL;
}
void BookmarkBarView::BookmarkNodeMoved(BookmarkModel* model,
const BookmarkNode* old_parent,
int old_index,
const BookmarkNode* new_parent,
int new_index) {
bool was_throbbing = throbbing_view_ &&
throbbing_view_ == DetermineViewToThrobFromRemove(old_parent, old_index);
if (was_throbbing)
throbbing_view_->StopThrobbing();
BookmarkNodeRemovedImpl(model, old_parent, old_index);
BookmarkNodeAddedImpl(model, new_parent, new_index);
if (was_throbbing)
StartThrobbing(new_parent->GetChild(new_index), false);
}
void BookmarkBarView::BookmarkNodeAdded(BookmarkModel* model,
const BookmarkNode* parent,
int index) {
BookmarkNodeAddedImpl(model, parent, index);
}
void BookmarkBarView::BookmarkNodeAddedImpl(BookmarkModel* model,
const BookmarkNode* parent,
int index) {
UpdateOtherBookmarksVisibility();
NotifyModelChanged();
if (parent != model_->GetBookmarkBarNode()) {
// We only care about nodes on the bookmark bar.
return;
}
DCHECK(index >= 0 && index <= GetBookmarkButtonCount());
const BookmarkNode* node = parent->GetChild(index);
if (!throbbing_view_ && sync_service_ && sync_service_->SetupInProgress()) {
StartThrobbing(node, true);
}
AddChildViewAt(CreateBookmarkButton(node), index);
UpdateColors();
Layout();
SchedulePaint();
}
void BookmarkBarView::BookmarkNodeRemoved(BookmarkModel* model,
const BookmarkNode* parent,
int old_index,
const BookmarkNode* node) {
BookmarkNodeRemovedImpl(model, parent, old_index);
}
void BookmarkBarView::BookmarkNodeRemovedImpl(BookmarkModel* model,
const BookmarkNode* parent,
int index) {
UpdateOtherBookmarksVisibility();
StopThrobbing(true);
// No need to start throbbing again as the bookmark bubble can't be up at
// the same time as the user reorders.
NotifyModelChanged();
if (parent != model_->GetBookmarkBarNode()) {
// We only care about nodes on the bookmark bar.
return;
}
DCHECK(index >= 0 && index < GetBookmarkButtonCount());
views::View* button = GetChildViewAt(index);
RemoveChildView(button);
MessageLoop::current()->DeleteSoon(FROM_HERE, button);
Layout();
SchedulePaint();
}
void BookmarkBarView::BookmarkNodeChanged(BookmarkModel* model,
const BookmarkNode* node) {
NotifyModelChanged();
BookmarkNodeChangedImpl(model, node);
}
void BookmarkBarView::BookmarkNodeChangedImpl(BookmarkModel* model,
const BookmarkNode* node) {
if (node->parent() != model_->GetBookmarkBarNode()) {
// We only care about nodes on the bookmark bar.
return;
}
int index = model_->GetBookmarkBarNode()->GetIndexOf(node);
DCHECK_NE(-1, index);
views::TextButton* button = GetBookmarkButton(index);
gfx::Size old_pref = button->GetPreferredSize();
ConfigureButton(node, button);
gfx::Size new_pref = button->GetPreferredSize();
if (old_pref.width() != new_pref.width()) {
Layout();
SchedulePaint();
} else if (button->IsVisible()) {
button->SchedulePaint();
}
}
void BookmarkBarView::BookmarkNodeChildrenReordered(BookmarkModel* model,
const BookmarkNode* node) {
NotifyModelChanged();
if (node != model_->GetBookmarkBarNode())
return; // We only care about reordering of the bookmark bar node.
// Remove the existing buttons.
while (GetBookmarkButtonCount()) {
views::View* button = GetChildViewAt(0);
RemoveChildView(button);
MessageLoop::current()->DeleteSoon(FROM_HERE, button);
}
// Create the new buttons.
for (int i = 0, child_count = node->child_count(); i < child_count; ++i)
AddChildViewAt(CreateBookmarkButton(node->GetChild(i)), i);
UpdateColors();
Layout();
SchedulePaint();
}
void BookmarkBarView::BookmarkNodeFaviconLoaded(BookmarkModel* model,
const BookmarkNode* node) {
BookmarkNodeChangedImpl(model, node);
}
void BookmarkBarView::WriteDragDataForView(View* sender,
const gfx::Point& press_pt,
ui::OSExchangeData* data) {
UserMetrics::RecordAction(UserMetricsAction("BookmarkBar_DragButton"),
profile_);
for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
if (sender == GetBookmarkButton(i)) {
views::TextButton* button = GetBookmarkButton(i);
gfx::CanvasSkia canvas(button->width(), button->height(), false);
button->PaintButton(&canvas, views::TextButton::PB_FOR_DRAG);
drag_utils::SetDragImageOnDataObject(canvas, button->size(), press_pt,
data);
WriteBookmarkDragData(model_->GetBookmarkBarNode()->GetChild(i), data);
return;
}
}
NOTREACHED();
}
int BookmarkBarView::GetDragOperationsForView(View* sender,
const gfx::Point& p) {
if (size_animation_->is_animating() ||
(size_animation_->GetCurrentValue() == 0 && !OnNewTabPage())) {
// Don't let the user drag while animating open or we're closed (and not on
// the new tab page, on the new tab page size_animation_ is always 0). This
// typically is only hit if the user does something to inadvertanty trigger
// dnd, such as pressing the mouse and hitting control-b.
return ui::DragDropTypes::DRAG_NONE;
}
for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
if (sender == GetBookmarkButton(i)) {
return bookmark_utils::BookmarkDragOperation(
profile_, model_->GetBookmarkBarNode()->GetChild(i));
}
}
NOTREACHED();
return ui::DragDropTypes::DRAG_NONE;
}
bool BookmarkBarView::CanStartDragForView(views::View* sender,
const gfx::Point& press_pt,
const gfx::Point& p) {
// Check if we have not moved enough horizontally but we have moved downward
// vertically - downward drag.
if (!View::ExceededDragThreshold(press_pt.x() - p.x(), 0) &&
press_pt.y() < p.y()) {
for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
if (sender == GetBookmarkButton(i)) {
const BookmarkNode* node = model_->GetBookmarkBarNode()->GetChild(i);
// If the folder button was dragged, show the menu instead.
if (node && node->is_folder()) {
views::MenuButton* menu_button =
static_cast<views::MenuButton*>(sender);
menu_button->Activate();
return false;
}
break;
}
}
}
return true;
}
void BookmarkBarView::WriteBookmarkDragData(const BookmarkNode* node,
ui::OSExchangeData* data) {
DCHECK(node && data);
BookmarkNodeData drag_data(node);
drag_data.Write(profile_, data);
}
void BookmarkBarView::RunMenu(views::View* view, const gfx::Point& pt) {
const BookmarkNode* node;
int start_index = 0;
if (view == other_bookmarked_button_) {
node = model_->other_node();
} else if (view == overflow_button_) {
node = model_->GetBookmarkBarNode();
start_index = GetFirstHiddenNodeIndex();
} else {
int button_index = GetIndexOf(view);
DCHECK_NE(-1, button_index);
node = model_->GetBookmarkBarNode()->GetChild(button_index);
}
bookmark_menu_ = new BookmarkMenuController(browser_, profile_,
page_navigator_, GetWindow()->GetNativeWindow(), node, start_index);
bookmark_menu_->set_observer(this);
bookmark_menu_->RunMenuAt(this, false);
}
void BookmarkBarView::ButtonPressed(views::Button* sender,
const views::Event& event) {
// Show the login wizard if the user clicked the re-login button.
if (sender->tag() == kSyncErrorButtonTag) {
DCHECK(sender == sync_error_button_);
DCHECK(sync_service_ && !sync_service_->IsManaged());
sync_service_->ShowErrorUI(GetWindow()->GetNativeWindow());
return;
}
const BookmarkNode* node;
if (sender->tag() == kOtherFolderButtonTag) {
node = model_->other_node();
} else {
int index = GetIndexOf(sender);
DCHECK_NE(-1, index);
node = model_->GetBookmarkBarNode()->GetChild(index);
}
DCHECK(page_navigator_);
WindowOpenDisposition disposition_from_event_flags =
event_utils::DispositionFromEventFlags(sender->mouse_event_flags());
if (node->is_url()) {
RecordAppLaunch(profile_, node->GetURL());
page_navigator_->OpenURL(node->GetURL(), GURL(),
disposition_from_event_flags, PageTransition::AUTO_BOOKMARK);
} else {
bookmark_utils::OpenAll(GetWindow()->GetNativeWindow(), profile_,
GetPageNavigator(), node, disposition_from_event_flags);
}
UserMetrics::RecordAction(UserMetricsAction("ClickedBookmarkBarURLButton"),
profile_);
}
void BookmarkBarView::ShowContextMenuForView(View* source,
const gfx::Point& p,
bool is_mouse_gesture) {
if (!model_->IsLoaded()) {
// Don't do anything if the model isn't loaded.
return;
}
const BookmarkNode* parent = NULL;
std::vector<const BookmarkNode*> nodes;
if (source == other_bookmarked_button_) {
parent = model_->other_node();
// Do this so the user can open all bookmarks. BookmarkContextMenu makes
// sure the user can edit/delete the node in this case.
nodes.push_back(parent);
} else if (source != this) {
// User clicked on one of the bookmark buttons, find which one they
// clicked on.
int bookmark_button_index = GetIndexOf(source);
DCHECK(bookmark_button_index != -1 &&
bookmark_button_index < GetBookmarkButtonCount());
const BookmarkNode* node =
model_->GetBookmarkBarNode()->GetChild(bookmark_button_index);
nodes.push_back(node);
parent = node->parent();
} else {
parent = model_->GetBookmarkBarNode();
nodes.push_back(parent);
}
// Browser may be null during testing.
PageNavigator* navigator =
browser() ? browser()->GetSelectedTabContents() : NULL;
BookmarkContextMenu controller(GetWindow()->GetNativeWindow(), GetProfile(),
navigator, parent, nodes);
controller.RunMenuAt(p);
}
views::View* BookmarkBarView::CreateBookmarkButton(const BookmarkNode* node) {
if (node->is_url()) {
BookmarkButton* button = new BookmarkButton(this, node->GetURL(),
UTF16ToWide(node->GetTitle()), GetProfile());
ConfigureButton(node, button);
return button;
} else {
views::MenuButton* button = new BookmarkFolderButton(this,
UTF16ToWide(node->GetTitle()), this, false);
button->SetIcon(GetFolderIcon());
ConfigureButton(node, button);
return button;
}
}
void BookmarkBarView::ConfigureButton(const BookmarkNode* node,
views::TextButton* button) {
button->SetText(UTF16ToWide(node->GetTitle()));
button->SetAccessibleName(node->GetTitle());
button->SetID(VIEW_ID_BOOKMARK_BAR_ELEMENT);
// We don't always have a theme provider (ui tests, for example).
if (GetThemeProvider()) {
button->SetEnabledColor(GetThemeProvider()->GetColor(
ThemeService::COLOR_BOOKMARK_TEXT));
}
button->ClearMaxTextSize();
button->SetContextMenuController(this);
button->SetDragController(this);
if (node->is_url()) {
if (model_->GetFavicon(node).width() != 0)
button->SetIcon(model_->GetFavicon(node));
else
button->SetIcon(*kDefaultFavicon);
}
button->set_max_width(kMaxButtonWidth);
}
bool BookmarkBarView::IsItemChecked(int id) const {
DCHECK(id == kAlwaysShowCommandID);
return profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar);
}
void BookmarkBarView::ExecuteCommand(int id) {
bookmark_utils::ToggleWhenVisible(profile_);
}
void BookmarkBarView::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
DCHECK(profile_);
switch (type.value) {
case NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED:
if (IsAlwaysShown()) {
size_animation_->Show();
} else {
size_animation_->Hide();
}
break;
case NotificationType::BOOKMARK_BUBBLE_SHOWN: {
StopThrobbing(true);
GURL url = *(Details<GURL>(details).ptr());
const BookmarkNode* node = model_->GetMostRecentlyAddedNodeForURL(url);
if (!node)
return; // Generally shouldn't happen.
StartThrobbing(node, false);
break;
}
case NotificationType::BOOKMARK_BUBBLE_HIDDEN:
StopThrobbing(false);
break;
default:
NOTREACHED();
break;
}
}
void BookmarkBarView::OnThemeChanged() {
UpdateColors();
}
void BookmarkBarView::NotifyModelChanged() {
if (model_changed_listener_)
model_changed_listener_->ModelChanged();
}
void BookmarkBarView::ShowDropFolderForNode(const BookmarkNode* node) {
if (bookmark_drop_menu_) {
if (bookmark_drop_menu_->node() == node) {
// Already showing for the specified node.
return;
}
bookmark_drop_menu_->Cancel();
}
views::MenuButton* menu_button = GetMenuButtonForNode(node);
if (!menu_button)
return;
int start_index = 0;
if (node == model_->GetBookmarkBarNode())
start_index = GetFirstHiddenNodeIndex();
drop_info_->is_menu_showing = true;
bookmark_drop_menu_ = new BookmarkMenuController(browser_, profile_,
page_navigator_, GetWindow()->GetNativeWindow(), node, start_index);
bookmark_drop_menu_->set_observer(this);
bookmark_drop_menu_->RunMenuAt(this, true);
}
void BookmarkBarView::StopShowFolderDropMenuTimer() {
if (show_folder_drop_menu_task_)
show_folder_drop_menu_task_->Cancel();
}
void BookmarkBarView::StartShowFolderDropMenuTimer(const BookmarkNode* node) {
if (testing_) {
// So that tests can run as fast as possible disable the delay during
// testing.
ShowDropFolderForNode(node);
return;
}
DCHECK(!show_folder_drop_menu_task_);
show_folder_drop_menu_task_ = new ShowFolderDropMenuTask(this, node);
int delay = views::GetMenuShowDelay();
MessageLoop::current()->PostDelayedTask(FROM_HERE,
show_folder_drop_menu_task_, delay);
}
int BookmarkBarView::CalculateDropOperation(const DropTargetEvent& event,
const BookmarkNodeData& data,
int* index,
bool* drop_on,
bool* is_over_overflow,
bool* is_over_other) {
DCHECK(model_);
DCHECK(model_->IsLoaded());
DCHECK(data.is_valid());
// The drop event uses the screen coordinates while the child Views are
// always laid out from left to right (even though they are rendered from
// right-to-left on RTL locales). Thus, in order to make sure the drop
// coordinates calculation works, we mirror the event's X coordinate if the
// locale is RTL.
int mirrored_x = GetMirroredXInView(event.x());
*index = -1;
*drop_on = false;
*is_over_other = *is_over_overflow = false;
bool found = false;
const int other_delta_x = mirrored_x - other_bookmarked_button_->x();
if (other_bookmarked_button_->IsVisible() && other_delta_x >= 0 &&
other_delta_x < other_bookmarked_button_->width()) {
// Mouse is over 'other' folder.
*is_over_other = true;
*drop_on = true;
found = true;
} else if (!GetBookmarkButtonCount()) {
// No bookmarks, accept the drop.
*index = 0;
int ops = data.GetFirstNode(profile_)
? ui::DragDropTypes::DRAG_MOVE
: ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK;
return bookmark_utils::PreferredDropOperation(event.source_operations(),
ops);
}
for (int i = 0; i < GetBookmarkButtonCount() &&
GetBookmarkButton(i)->IsVisible() && !found; i++) {
views::TextButton* button = GetBookmarkButton(i);
int button_x = mirrored_x - button->x();
int button_w = button->width();
if (button_x < button_w) {
found = true;
const BookmarkNode* node = model_->GetBookmarkBarNode()->GetChild(i);
if (node->is_folder()) {
if (button_x <= views::kDropBetweenPixels) {
*index = i;
} else if (button_x < button_w - views::kDropBetweenPixels) {
*index = i;
*drop_on = true;
} else {
*index = i + 1;
}
} else if (button_x < button_w / 2) {
*index = i;
} else {
*index = i + 1;
}
break;
}
}
if (!found) {
if (overflow_button_->IsVisible()) {
// Are we over the overflow button?
int overflow_delta_x = mirrored_x - overflow_button_->x();
if (overflow_delta_x >= 0 &&
overflow_delta_x < overflow_button_->width()) {
// Mouse is over overflow button.
*index = GetFirstHiddenNodeIndex();
*is_over_overflow = true;
} else if (overflow_delta_x < 0) {
// Mouse is after the last visible button but before overflow button;
// use the last visible index.
*index = GetFirstHiddenNodeIndex();
} else {
return ui::DragDropTypes::DRAG_NONE;
}
} else if (!other_bookmarked_button_->IsVisible() ||
mirrored_x < other_bookmarked_button_->x()) {
// Mouse is after the last visible button but before more recently
// bookmarked; use the last visible index.
*index = GetFirstHiddenNodeIndex();
} else {
return ui::DragDropTypes::DRAG_NONE;
}
}
if (*drop_on) {
const BookmarkNode* parent =
*is_over_other ? model_->other_node() :
model_->GetBookmarkBarNode()->GetChild(*index);
int operation =
bookmark_utils::BookmarkDropOperation(profile_, event, data, parent,
parent->child_count());
if (!operation && !data.has_single_url() &&
data.GetFirstNode(profile_) == parent) {
// Don't open a menu if the node being dragged is the the menu to
// open.
*drop_on = false;
}
return operation;
}
return bookmark_utils::BookmarkDropOperation(profile_, event, data,
model_->GetBookmarkBarNode(),
*index);
}
int BookmarkBarView::GetFirstHiddenNodeIndex() {
const int bb_count = GetBookmarkButtonCount();
for (int i = 0; i < bb_count; ++i) {
if (!GetBookmarkButton(i)->IsVisible())
return i;
}
return bb_count;
}
void BookmarkBarView::StartThrobbing(const BookmarkNode* node,
bool overflow_only) {
DCHECK(!throbbing_view_);
// Determine which visible button is showing the bookmark (or is an ancestor
// of the bookmark).
const BookmarkNode* bbn = model_->GetBookmarkBarNode();
const BookmarkNode* parent_on_bb = node;
while (parent_on_bb) {
const BookmarkNode* parent = parent_on_bb->parent();
if (parent == bbn)
break;
parent_on_bb = parent;
}
if (parent_on_bb) {
int index = bbn->GetIndexOf(parent_on_bb);
if (index >= GetFirstHiddenNodeIndex()) {
// Node is hidden, animate the overflow button.
throbbing_view_ = overflow_button_;
} else if (!overflow_only) {
throbbing_view_ = static_cast<CustomButton*>(GetChildViewAt(index));
}
} else if (!overflow_only) {
throbbing_view_ = other_bookmarked_button_;
}
// Use a large number so that the button continues to throb.
if (throbbing_view_)
throbbing_view_->StartThrobbing(std::numeric_limits<int>::max());
}
views::CustomButton* BookmarkBarView::DetermineViewToThrobFromRemove(
const BookmarkNode* parent,
int old_index) {
const BookmarkNode* bbn = model_->GetBookmarkBarNode();
const BookmarkNode* old_node = parent;
int old_index_on_bb = old_index;
while (old_node && old_node != bbn) {
const BookmarkNode* parent = old_node->parent();
if (parent == bbn) {
old_index_on_bb = bbn->GetIndexOf(old_node);
break;
}
old_node = parent;
}
if (old_node) {
if (old_index_on_bb >= GetFirstHiddenNodeIndex()) {
// Node is hidden, animate the overflow button.
return overflow_button_;
}
return static_cast<CustomButton*>(GetChildViewAt(old_index_on_bb));
}
// Node wasn't on the bookmark bar, use the other bookmark button.
return other_bookmarked_button_;
}
int BookmarkBarView::GetBookmarkButtonCount() {
// We contain five non-bookmark button views: other bookmarks, bookmarks
// separator, chevrons (for overflow), the instruction label and the sync
// error button.
return child_count() - 5;
}
void BookmarkBarView::StopThrobbing(bool immediate) {
if (!throbbing_view_)
return;
// If not immediate, cycle through 2 more complete cycles.
throbbing_view_->StartThrobbing(immediate ? 0 : 4);
throbbing_view_ = NULL;
}
// static
std::wstring BookmarkBarView::CreateToolTipForURLAndTitle(
const gfx::Point& screen_loc,
const GURL& url,
const std::wstring& title,
Profile* profile) {
int max_width = views::TooltipManager::GetMaxWidth(screen_loc.x(),
screen_loc.y());
gfx::Font tt_font = views::TooltipManager::GetDefaultFont();
std::wstring result;
// First the title.
if (!title.empty()) {
std::wstring localized_title = title;
base::i18n::AdjustStringForLocaleDirection(&localized_title);
result.append(UTF16ToWideHack(ui::ElideText(WideToUTF16Hack(
localized_title), tt_font, max_width, false)));
}
// Only show the URL if the url and title differ.
if (title != UTF8ToWide(url.spec())) {
if (!result.empty())
result.append(views::TooltipManager::GetLineSeparator());
// We need to explicitly specify the directionality of the URL's text to
// make sure it is treated as an LTR string when the context is RTL. For
// example, the URL "http://www.yahoo.com/" appears as
// "/http://www.yahoo.com" when rendered, as is, in an RTL context since
// the Unicode BiDi algorithm puts certain characters on the left by
// default.
std::string languages = profile->GetPrefs()->GetString(
prefs::kAcceptLanguages);
string16 elided_url(ui::ElideUrl(url, tt_font, max_width, languages));
elided_url = base::i18n::GetDisplayStringInLTRDirectionality(elided_url);
result.append(UTF16ToWideHack(elided_url));
}
return result;
}
void BookmarkBarView::UpdateColors() {
// We don't always have a theme provider (ui tests, for example).
const ui::ThemeProvider* theme_provider = GetThemeProvider();
if (!theme_provider)
return;
SkColor text_color =
theme_provider->GetColor(ThemeService::COLOR_BOOKMARK_TEXT);
for (int i = 0; i < GetBookmarkButtonCount(); ++i)
GetBookmarkButton(i)->SetEnabledColor(text_color);
other_bookmarked_button()->SetEnabledColor(text_color);
}
void BookmarkBarView::UpdateOtherBookmarksVisibility() {
bool has_other_children = model_->other_node()->child_count() > 0;
if (has_other_children == other_bookmarked_button_->IsVisible())
return;
other_bookmarked_button_->SetVisible(has_other_children);
bookmarks_separator_view_->SetVisible(has_other_children);
Layout();
SchedulePaint();
}
gfx::Size BookmarkBarView::LayoutItems(bool compute_bounds_only) {
gfx::Size prefsize;
if (!parent() && !compute_bounds_only)
return prefsize;
int x = kLeftMargin;
int top_margin = IsDetached() ? kDetachedTopMargin : 0;
int y = top_margin;
int width = View::width() - kRightMargin - kLeftMargin;
int height = -top_margin - kBottomMargin;
int separator_margin = kSeparatorMargin;
if (OnNewTabPage()) {
double current_state = 1 - size_animation_->GetCurrentValue();
x += static_cast<int>(kNewtabHorizontalPadding * current_state);
y += static_cast<int>(kNewtabVerticalPadding * current_state);
width -= static_cast<int>(kNewtabHorizontalPadding * current_state);
height += View::height() -
static_cast<int>(kNewtabVerticalPadding * 2 * current_state);
separator_margin -= static_cast<int>(kSeparatorMargin * current_state);
} else {
// For the attached appearance, pin the content to the bottom of the bar
// when animating in/out, as shrinking its height instead looks weird. This
// also matches how we layout infobars.
y += View::height() - kBarHeight;
height += kBarHeight;
}
gfx::Size other_bookmarked_pref =
other_bookmarked_button_->IsVisible() ?
other_bookmarked_button_->GetPreferredSize() : gfx::Size();
gfx::Size overflow_pref = overflow_button_->GetPreferredSize();
gfx::Size bookmarks_separator_pref =
bookmarks_separator_view_->GetPreferredSize();
int sync_error_total_width = 0;
gfx::Size sync_error_button_pref = sync_error_button_->GetPreferredSize();
if (sync_ui_util::ShouldShowSyncErrorButton(sync_service_)) {
sync_error_total_width += kButtonPadding + sync_error_button_pref.width();
}
int max_x = width - overflow_pref.width() - kButtonPadding -
bookmarks_separator_pref.width() - sync_error_total_width;
if (other_bookmarked_button_->IsVisible())
max_x -= other_bookmarked_pref.width() + kButtonPadding;
// Next, layout out the buttons. Any buttons that are placed beyond the
// visible region and made invisible.
if (GetBookmarkButtonCount() == 0 && model_ && model_->IsLoaded()) {
gfx::Size pref = instructions_->GetPreferredSize();
if (!compute_bounds_only) {
instructions_->SetBounds(
x + kInstructionsPadding, y,
std::min(static_cast<int>(pref.width()),
max_x - x),
height);
instructions_->SetVisible(true);
}
} else {
if (!compute_bounds_only)
instructions_->SetVisible(false);
for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
views::View* child = GetChildViewAt(i);
gfx::Size pref = child->GetPreferredSize();
int next_x = x + pref.width() + kButtonPadding;
if (!compute_bounds_only) {
child->SetVisible(next_x < max_x);
child->SetBounds(x, y, pref.width(), height);
}
x = next_x;
}
}
// Layout the right side of the bar.
const bool all_visible =
(GetBookmarkButtonCount() == 0 ||
GetChildViewAt(GetBookmarkButtonCount() - 1)->IsVisible());
// Layout the right side buttons.
if (!compute_bounds_only)
x = max_x + kButtonPadding;
else
x += kButtonPadding;
// The overflow button.
if (!compute_bounds_only) {
overflow_button_->SetBounds(x, y, overflow_pref.width(), height);
overflow_button_->SetVisible(!all_visible);
}
x += overflow_pref.width();
// Separator.
if (bookmarks_separator_view_->IsVisible()) {
if (!compute_bounds_only) {
bookmarks_separator_view_->SetBounds(x,
y - top_margin,
bookmarks_separator_pref.width(),
height + top_margin + kBottomMargin -
separator_margin);
}
x += bookmarks_separator_pref.width();
}
// The other bookmarks button.
if (other_bookmarked_button_->IsVisible()) {
if (!compute_bounds_only) {
other_bookmarked_button_->SetBounds(x, y, other_bookmarked_pref.width(),
height);
}
x += other_bookmarked_pref.width() + kButtonPadding;
}
// Set the real bounds of the sync error button only if it needs to appear on
// the bookmarks bar.
if (sync_ui_util::ShouldShowSyncErrorButton(sync_service_)) {
x += kButtonPadding;
if (!compute_bounds_only) {
sync_error_button_->SetBounds(
x, y, sync_error_button_pref.width(), height);
sync_error_button_->SetVisible(true);
}
x += sync_error_button_pref.width();
} else if (!compute_bounds_only) {
sync_error_button_->SetBounds(x, y, 0, height);
sync_error_button_->SetVisible(false);
}
// Set the preferred size computed so far.
if (compute_bounds_only) {
x += kRightMargin;
prefsize.set_width(x);
if (OnNewTabPage()) {
x += static_cast<int>(
kNewtabHorizontalPadding * (1 - size_animation_->GetCurrentValue()));
prefsize.set_height(kBarHeight +
static_cast<int>((kNewtabBarHeight - kBarHeight) *
(1 - size_animation_->GetCurrentValue())));
} else {
prefsize.set_height(
static_cast<int>(kBarHeight * size_animation_->GetCurrentValue()));
}
}
return prefsize;
}
views::TextButton* BookmarkBarView::CreateSyncErrorButton() {
views::TextButton* sync_error_button =
new views::TextButton(this, UTF16ToWide(
l10n_util::GetStringUTF16(IDS_SYNC_BOOKMARK_BAR_ERROR)));
sync_error_button->set_tag(kSyncErrorButtonTag);
// The tooltip is the only way we have to display text explaining the error
// to the user.
sync_error_button->SetTooltipText(
UTF16ToWide(l10n_util::GetStringUTF16(IDS_SYNC_BOOKMARK_BAR_ERROR_DESC)));
sync_error_button->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_ACCNAME_SYNC_ERROR_BUTTON));
sync_error_button->SetIcon(
*ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_WARNING));
return sync_error_button;
}