diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-09 07:35:32 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-09 07:35:32 +0000 |
commit | 213dac2f0bff9162502fe325b6ebb85a255efcb2 (patch) | |
tree | 3640cb1f19976e38677b8632537d2d41f8444d0f /chrome/browser/ui/views/bookmark_bar_view.cc | |
parent | 6de53d401aa8dc6c7e0a9874c71a95ce88ade50d (diff) | |
download | chromium_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.cc | 1734 |
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; +} |