summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/views/bookmark_bar_view.cc
diff options
context:
space:
mode:
authorben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-11-09 07:35:32 +0000
committerben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-11-09 07:35:32 +0000
commit213dac2f0bff9162502fe325b6ebb85a255efcb2 (patch)
tree3640cb1f19976e38677b8632537d2d41f8444d0f /chrome/browser/ui/views/bookmark_bar_view.cc
parent6de53d401aa8dc6c7e0a9874c71a95ce88ade50d (diff)
downloadchromium_src-213dac2f0bff9162502fe325b6ebb85a255efcb2.zip
chromium_src-213dac2f0bff9162502fe325b6ebb85a255efcb2.tar.gz
chromium_src-213dac2f0bff9162502fe325b6ebb85a255efcb2.tar.bz2
Move browser/views to browser/ui/views
TBR=brettw BUG=none TEST=none Review URL: http://codereview.chromium.org/4694005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@65508 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/ui/views/bookmark_bar_view.cc')
-rw-r--r--chrome/browser/ui/views/bookmark_bar_view.cc1734
1 files changed, 1734 insertions, 0 deletions
diff --git a/chrome/browser/ui/views/bookmark_bar_view.cc b/chrome/browser/ui/views/bookmark_bar_view.cc
new file mode 100644
index 0000000..9188aa5
--- /dev/null
+++ b/chrome/browser/ui/views/bookmark_bar_view.cc
@@ -0,0 +1,1734 @@
+// Copyright (c) 2010 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/views/bookmark_bar_view.h"
+
+#include <algorithm>
+#include <limits>
+#include <set>
+#include <vector>
+
+#include "app/l10n_util.h"
+#include "app/os_exchange_data.h"
+#include "app/resource_bundle.h"
+#include "app/text_elider.h"
+#include "base/i18n/rtl.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.h"
+#include "chrome/browser/browser_shutdown.h"
+#include "chrome/browser/browser_thread.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "chrome/browser/metrics/user_metrics.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
+#include "chrome/browser/sync/sync_ui_util.h"
+#include "chrome/browser/tab_contents/page_navigator.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/themes/browser_theme_provider.h"
+#include "chrome/browser/view_ids.h"
+#include "chrome/browser/views/bookmark_context_menu.h"
+#include "chrome/browser/views/event_utils.h"
+#include "chrome/browser/views/frame/browser_view.h"
+#include "chrome/browser/views/location_bar/location_bar_view.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/page_transition_types.h"
+#include "chrome/common/pref_names.h"
+#include "gfx/canvas_skia.h"
+#include "grit/app_resources.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.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/view_constants.h"
+#include "views/widget/tooltip_manager.h"
+#include "views/widget/widget.h"
+#include "views/window/window.h"
+
+#if defined(OS_WIN)
+#include "chrome/browser/views/importer_view.h"
+#endif
+
+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;
+
+// Delay during drag and drop before the menu pops up. This is only used if
+// we can't get the value from the OS.
+static const int kShowFolderDropMenuDelay = 400;
+
+// 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 {
+
+// Returns the tooltip text for the specified url and title. The returned
+// text is clipped to fit within the bounds of the monitor.
+//
+// Note that we adjust the direction of both the URL and the title based on the
+// locale so that pure LTR strings are displayed properly in RTL locales.
+static std::wstring CreateToolTipForURLAndTitle(const gfx::Point& screen_loc,
+ const GURL& url,
+ const std::wstring& title,
+ const std::wstring& languages) {
+ 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;
+ if (!base::i18n::AdjustStringForLocaleDirection(title, &localized_title))
+ localized_title = title;
+ result.append(UTF16ToWideHack(gfx::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.
+ string16 elided_url(gfx::ElideUrl(url, tt_font, max_width, languages));
+ elided_url = base::i18n::GetDisplayStringInLTRDirectionality(elided_url);
+ result.append(UTF16ToWideHack(elided_url));
+ }
+ return result;
+}
+
+// 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 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 = CreateToolTipForURLAndTitle(location, url_, text(),
+ UTF8ToWide(profile_->GetPrefs()->GetString(prefs::kAcceptLanguages)));
+ return !tooltip->empty();
+ }
+
+ virtual bool IsTriggerableEvent(const views::MouseEvent& e) {
+ return event_utils::IsPossibleDispositionEvent(e);
+ }
+
+ private:
+ const GURL& url_;
+ Profile* profile_;
+ scoped_ptr<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 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.GetFlags() == views::MouseEvent::EF_LEFT_BUTTON_DOWN ||
+ e.GetFlags() == views::MouseEvent::EF_RIGHT_BUTTON_DOWN)
+ return false;
+
+ WindowOpenDisposition disposition(
+ event_utils::DispositionFromEventFlags(e.GetFlags()));
+ return disposition != CURRENT_TAB;
+ }
+
+ virtual void Paint(gfx::Canvas* canvas) {
+ views::MenuButton::Paint(canvas, false);
+ }
+
+ private:
+ scoped_ptr<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);
+};
+
+} // 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 group
+ // 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.
+ BookmarkDragData data;
+};
+
+// ButtonSeparatorView --------------------------------------------------------
+
+class BookmarkBarView::ButtonSeparatorView : public views::View {
+ public:
+ ButtonSeparatorView() {}
+ virtual ~ButtonSeparatorView() {}
+
+ virtual void Paint(gfx::Canvas* canvas) {
+ DetachableToolbarView::PaintVerticalDivider(
+ canvas, kSeparatorStartX, height(), 1,
+ DetachableToolbarView::kEdgeDividerColor,
+ DetachableToolbarView::kMiddleDividerColor,
+ GetThemeProvider()->GetColor(BrowserThemeProvider::COLOR_TOOLBAR));
+ }
+
+ virtual gfx::Size GetPreferredSize() {
+ // We get the full height of the bookmark bar, so that the height returned
+ // here doesn't matter.
+ return gfx::Size(kSeparatorWidth, 1);
+ }
+
+ virtual AccessibilityTypes::Role GetAccessibleRole() {
+ return 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 groups.
+static const SkBitmap& GetGroupIcon() {
+ 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>(static_cast<double>
+ (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::DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current) {
+ Layout();
+}
+
+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, DidChangeBounds 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(MirroredLeftPointForRect(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<OSExchangeData::CustomFormat>* custom_formats) {
+ if (!model_ || !model_->IsLoaded())
+ return false;
+ *formats = OSExchangeData::URL;
+ custom_formats->insert(BookmarkDragData::GetBookmarkCustomFormat());
+ return true;
+}
+
+bool BookmarkBarView::AreDropTypesRequired() {
+ return true;
+}
+
+bool BookmarkBarView::CanDrop(const OSExchangeData& data) {
+ if (!model_ || !model_->IsLoaded())
+ 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 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 BookmarkDragData 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->GetChildCount();
+ } else if (drop_on) {
+ parent_node = root->GetChild(index);
+ index = parent_node->GetChildCount();
+ } else {
+ parent_node = root;
+ }
+ return bookmark_utils::PerformBookmarkDrop(profile_, data, parent_node,
+ index);
+}
+
+void BookmarkBarView::ShowContextMenu(const gfx::Point& p,
+ bool is_mouse_gesture) {
+ ShowContextMenu(this, p, is_mouse_gesture);
+}
+
+bool BookmarkBarView::IsAccessibleViewTraversable(views::View* view) {
+ return view != bookmarks_separator_view_ && view != instructions_;
+}
+
+AccessibilityTypes::Role BookmarkBarView::GetAccessibleRole() {
+ return AccessibilityTypes::ROLE_TOOLBAR;
+}
+
+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 {
+ return OnNewTabPage() && (size_animation_->GetCurrentValue() != 1);
+}
+
+bool BookmarkBarView::IsOnTop() const {
+ return true;
+}
+
+bool BookmarkBarView::IsAlwaysShown() const {
+ return profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar);
+}
+
+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>(static_cast<double>(kToolbarOverlap) *
+ size_animation_->GetCurrentValue());
+}
+
+void BookmarkBarView::AnimationProgressed(const Animation* animation) {
+ if (browser_)
+ browser_->ToolbarSizeChanged(true);
+}
+
+void BookmarkBarView::AnimationEnded(const Animation* animation) {
+ if (browser_)
+ browser_->ToolbarSizeChanged(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(MirroredXCoordinateInsideView(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_->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()->IndexOfChild(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() {
+#if defined(OS_WIN)
+ views::Window::CreateChromeWindow(
+ GetWindow()->GetNativeWindow(),
+ gfx::Rect(),
+ new ImporterView(profile_, importer::FAVORITES))->Show();
+#endif
+}
+
+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();
+ bookmarks_separator_view_->SetAccessibleName(
+ l10n_util::GetString(IDS_ACCNAME_SEPARATOR));
+ AddChildView(bookmarks_separator_view_);
+
+ instructions_ = new BookmarkBarInstructionsView(this);
+ AddChildView(instructions_);
+
+ SetContextMenuController(this);
+
+ size_animation_.reset(new SlideAnimation(this));
+}
+
+MenuButton* BookmarkBarView::CreateOtherBookmarkedButton() {
+ MenuButton* button = new BookmarkFolderButton(
+ this, l10n_util::GetString(IDS_BOOMARK_BAR_OTHER_BOOKMARKED), this,
+ false);
+ button->SetID(VIEW_ID_OTHER_BOOKMARKS);
+ button->SetIcon(GetGroupIcon());
+ button->SetContextMenuController(this);
+ button->set_tag(kOtherFolderButtonTag);
+ button->SetAccessibleName(
+ l10n_util::GetString(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::GetString(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->GetChildCount(); i < child_count; ++i)
+ AddChildView(i, CreateBookmarkButton(node->GetChild(i)));
+ UpdateColors();
+ 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) {
+ BookmarkNodeRemovedImpl(model, old_parent, old_index);
+ BookmarkNodeAddedImpl(model, new_parent, new_index);
+}
+
+void BookmarkBarView::BookmarkNodeAdded(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) {
+ BookmarkNodeAddedImpl(model, parent, index);
+}
+
+void BookmarkBarView::BookmarkNodeAddedImpl(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) {
+ 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);
+ }
+ AddChildView(index, CreateBookmarkButton(node));
+ 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) {
+ 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->GetParent() != model_->GetBookmarkBarNode()) {
+ // We only care about nodes on the bookmark bar.
+ return;
+ }
+ int index = model_->GetBookmarkBarNode()->IndexOfChild(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->GetChildCount(); i < child_count; ++i)
+ AddChildView(i, CreateBookmarkButton(node->GetChild(i)));
+ UpdateColors();
+
+ Layout();
+ SchedulePaint();
+}
+
+void BookmarkBarView::BookmarkNodeFavIconLoaded(BookmarkModel* model,
+ const BookmarkNode* node) {
+ BookmarkNodeChangedImpl(model, node);
+}
+
+void BookmarkBarView::WriteDragData(View* sender,
+ const gfx::Point& press_pt,
+ 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->Paint(&canvas, true);
+ drag_utils::SetDragImageOnDataObject(canvas, button->size(),
+ press_pt, data);
+ WriteDragData(model_->GetBookmarkBarNode()->GetChild(i), data);
+ return;
+ }
+ }
+ NOTREACHED();
+}
+
+int BookmarkBarView::GetDragOperations(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 DragDropTypes::DRAG_NONE;
+ }
+
+ for (int i = 0; i < GetBookmarkButtonCount(); ++i) {
+ if (sender == GetBookmarkButton(i)) {
+ return bookmark_utils::BookmarkDragOperation(
+ model_->GetBookmarkBarNode()->GetChild(i));
+ }
+ }
+ NOTREACHED();
+ return DragDropTypes::DRAG_NONE;
+}
+
+bool BookmarkBarView::CanStartDrag(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::WriteDragData(const BookmarkNode* node,
+ OSExchangeData* data) {
+ DCHECK(node && data);
+ BookmarkDragData 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 = GetChildIndex(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_->ShowLoginDialog(GetWindow()->GetNativeWindow());
+ return;
+ }
+
+ const BookmarkNode* node;
+ if (sender->tag() == kOtherFolderButtonTag) {
+ node = model_->other_node();
+ } else {
+ int index = GetChildIndex(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()) {
+ 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::ShowContextMenu(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 = GetChildIndex(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->GetParent();
+ } 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(GetGroupIcon());
+ ConfigureButton(node, button);
+ return button;
+ }
+}
+
+void BookmarkBarView::ConfigureButton(const BookmarkNode* node,
+ views::TextButton* button) {
+ button->SetText(UTF16ToWide(node->GetTitle()));
+ button->SetAccessibleName(UTF16ToWide(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(
+ BrowserThemeProvider::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 = View::GetMenuShowDelay();
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ show_folder_drop_menu_task_, delay);
+}
+
+int BookmarkBarView::CalculateDropOperation(const DropTargetEvent& event,
+ const BookmarkDragData& 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 = MirroredXCoordinateInsideView(event.x());
+
+ *index = -1;
+ *drop_on = false;
+ *is_over_other = *is_over_overflow = false;
+
+ if (event.y() < other_bookmarked_button_->y() ||
+ event.y() >= other_bookmarked_button_->y() +
+ other_bookmarked_button_->height()) {
+ // Mouse isn't over a button.
+ return DragDropTypes::DRAG_NONE;
+ }
+
+ bool found = false;
+ const int other_delta_x = mirrored_x - other_bookmarked_button_->x();
+ if (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_)
+ ? DragDropTypes::DRAG_MOVE
+ : DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK;
+ return
+ bookmark_utils::PreferredDropOperation(event.GetSourceOperations(),
+ 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 DragDropTypes::DRAG_NONE;
+ }
+ } else if (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 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->GetChildCount());
+ 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->GetParent();
+ if (parent == bbn)
+ break;
+ parent_on_bb = parent;
+ }
+ if (parent_on_bb) {
+ int index = bbn->IndexOfChild(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());
+}
+
+int BookmarkBarView::GetBookmarkButtonCount() {
+ // We contain at least four non-bookmark button views: other bookmarks,
+ // bookmarks separator, chevrons (for overflow), the instruction label and
+ // the sync error button.
+ return GetChildViewCount() - 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;
+}
+
+void BookmarkBarView::UpdateColors() {
+ // We don't always have a theme provider (ui tests, for example).
+ const ThemeProvider* theme_provider = GetThemeProvider();
+ if (!theme_provider)
+ return;
+ SkColor text_color =
+ theme_provider->GetColor(BrowserThemeProvider::COLOR_BOOKMARK_TEXT);
+ for (int i = 0; i < GetBookmarkButtonCount(); ++i)
+ GetBookmarkButton(i)->SetEnabledColor(text_color);
+ other_bookmarked_button()->SetEnabledColor(text_color);
+}
+
+gfx::Size BookmarkBarView::LayoutItems(bool compute_bounds_only) {
+ gfx::Size prefsize;
+ if (!GetParent() && !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 = View::height() - top_margin - kBottomMargin;
+ int separator_margin = kSeparatorMargin;
+
+ if (OnNewTabPage()) {
+ double current_state = 1 - size_animation_->GetCurrentValue();
+ x += static_cast<int>(static_cast<double>
+ (kNewtabHorizontalPadding) * current_state);
+ y += static_cast<int>(static_cast<double>
+ (kNewtabVerticalPadding) * current_state);
+ width -= static_cast<int>(static_cast<double>
+ (kNewtabHorizontalPadding) * current_state);
+ height -= static_cast<int>(static_cast<double>
+ (kNewtabVerticalPadding * 2) * current_state);
+ separator_margin -= static_cast<int>(static_cast<double>
+ (kSeparatorMargin) * current_state);
+ }
+
+ 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();
+
+ 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();
+ }
+ const int max_x = width - other_bookmarked_pref.width() - kButtonPadding -
+ overflow_pref.width() - kButtonPadding -
+ bookmarks_separator_pref.width() - sync_error_total_width;
+
+ // 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 (!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 (!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>(static_cast<double>(kNewtabHorizontalPadding) *
+ (1 - size_animation_->GetCurrentValue()));
+ prefsize.set_height(kBarHeight + static_cast<int>(static_cast<double>
+ (kNewtabBarHeight - kBarHeight) *
+ (1 - size_animation_->GetCurrentValue())));
+ } else {
+ prefsize.set_height(static_cast<int>(static_cast<double>(kBarHeight) *
+ size_animation_->GetCurrentValue()));
+ }
+ }
+ return prefsize;
+}
+
+views::TextButton* BookmarkBarView::CreateSyncErrorButton() {
+ views::TextButton* sync_error_button =
+ new views::TextButton(this,
+ l10n_util::GetString(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(
+ l10n_util::GetString(IDS_SYNC_BOOKMARK_BAR_ERROR_DESC));
+ sync_error_button->SetAccessibleName(
+ l10n_util::GetString(IDS_ACCNAME_SYNC_ERROR_BUTTON));
+ sync_error_button->SetIcon(
+ *ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_WARNING));
+ return sync_error_button;
+}