| // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/views/bookmark_bar_view.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <set> |
| #include <vector> |
| |
| #include "app/gfx/canvas.h" |
| #include "app/gfx/text_elider.h" |
| #include "app/l10n_util.h" |
| #include "app/os_exchange_data.h" |
| #include "app/resource_bundle.h" |
| #include "base/string_util.h" |
| #include "chrome/browser/bookmarks/bookmark_utils.h" |
| #include "chrome/browser/browser.h" |
| #include "chrome/browser/browser_theme_provider.h" |
| #include "chrome/browser/metrics/user_metrics.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/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_view.h" |
| #include "chrome/common/notification_service.h" |
| #include "chrome/common/page_transition_types.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/pref_service.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/button/menu_button.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" |
| |
| 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 double kToolbarOverlap = 4.0; |
| |
| // Margins around the content. |
| static const int kTopMargin = 1; |
| 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 = 29; |
| |
| // 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; |
| |
| // Color of the instructional text. |
| static const SkColor kInstructionsColor = SkColorSetRGB(128, 128, 142); |
| |
| // 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 (l10n_util::AdjustStringForLocaleDirection(title, &localized_title)) |
| result.append(gfx::ElideText(localized_title, tt_font, max_width)); |
| else |
| result.append(gfx::ElideText(title, tt_font, max_width)); |
| } |
| |
| // Only show the URL if the url and title differ. |
| if (title != UTF8ToWide(url.spec())) { |
| if (!result.empty()) |
| result.append(views::TooltipManager::GetLineSeparator()); |
| |
| // We need to explicitly specify the directionality of the URL's text to |
| // make sure it is treated as an LTR string when the context is RTL. For |
| // example, the URL "http://www.yahoo.com/" appears as |
| // "/http://www.yahoo.com" when rendered, as is, in an RTL context since |
| // the Unicode BiDi algorithm puts certain characters on the left by |
| // default. |
| std::wstring elided_url(gfx::ElideUrl(url, tt_font, max_width, languages)); |
| if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) |
| l10n_util::WrapStringWithLTRFormatting(&elided_url); |
| result.append(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(int x, int y, std::wstring* tooltip) { |
| gfx::Point location(x, y); |
| ConvertPointToScreen(this, &location); |
| *tooltip = CreateToolTipForURLAndTitle( |
| gfx::Point(location.x(), location.y()), url_, text(), |
| 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); |
| }; |
| |
| } // 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 bool GetAccessibleName(std::wstring* name) { |
| DCHECK(name); |
| |
| if (!accessible_name_.empty()) { |
| name->assign(accessible_name_); |
| return true; |
| } |
| return false; |
| } |
| |
| virtual bool GetAccessibleRole(AccessibilityTypes::Role* role) { |
| DCHECK(role); |
| |
| *role = AccessibilityTypes::ROLE_SEPARATOR; |
| return true; |
| } |
| |
| virtual void SetAccessibleName(const std::wstring& name) { |
| accessible_name_.assign(name); |
| } |
| |
| private: |
| // Storage of strings needed for accessibility. |
| std::wstring accessible_name_; |
| |
| 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), |
| 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); |
| |
| if (IsAlwaysShown()) |
| size_animation_->Reset(1); |
| else |
| size_animation_->Reset(0); |
| } |
| |
| BookmarkBarView::~BookmarkBarView() { |
| NotifyModelChanged(); |
| if (model_) |
| model_->RemoveObserver(this); |
| 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()); |
| |
| model_ = profile_->GetBookmarkModel(); |
| 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 (ShouldShowSyncErrorButton()) |
| 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); |
| } |
| |
| bool BookmarkBarView::GetAccessibleName(std::wstring* name) { |
| DCHECK(name); |
| |
| if (!accessible_name_.empty()) { |
| name->assign(accessible_name_); |
| return true; |
| } |
| return false; |
| } |
| |
| bool BookmarkBarView::GetAccessibleRole(AccessibilityTypes::Role* role) { |
| DCHECK(role); |
| |
| *role = AccessibilityTypes::ROLE_TOOLBAR; |
| return true; |
| } |
| |
| void BookmarkBarView::SetAccessibleName(const std::wstring& name) { |
| accessible_name_.assign(name); |
| } |
| |
| 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) { |
| return static_cast<int>(kToolbarOverlap * |
| (return_max ? 1.0 : 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 (UILayoutIsRightToLeft()) { |
| if (*anchor == MenuItemView::TOPRIGHT) |
| *anchor = MenuItemView::TOPLEFT; |
| else |
| *anchor = MenuItemView::TOPRIGHT; |
| } |
| |
| if (button == overflow_button_) |
| *start_index = GetFirstHiddenNodeIndex(); |
| else |
| *start_index = 0; |
| } |
| |
| 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); |
| |
| other_bookmarked_button_ = CreateOtherBookmarkedButton(); |
| AddChildView(other_bookmarked_button_); |
| |
| sync_error_button_ = CreateSyncErrorButton(); |
| AddChildView(sync_error_button_); |
| |
| overflow_button_ = CreateOverflowButton(); |
| AddChildView(overflow_button_); |
| |
| bookmarks_separator_view_ = new ButtonSeparatorView(); |
| bookmarks_separator_view_->SetAccessibleName( |
| l10n_util::GetString(IDS_ACCNAME_SEPARATOR)); |
| AddChildView(bookmarks_separator_view_); |
| |
| instructions_ = new views::Label( |
| l10n_util::GetString(IDS_BOOKMARKS_NO_ITEMS), |
| rb.GetFont(ResourceBundle::BaseFont)); |
| |
| 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->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 MenuButton(NULL, std::wstring(), this, false); |
| 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; |
| } |
| |
| 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::Loaded(BookmarkModel* model) { |
| 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) { |
| // The bookmark model should never be deleted before us. This code exists |
| // to check for regressions in shutdown code and not crash. |
| 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) { |
| StopThrobbing(true); |
| BookmarkNodeRemovedImpl(model, old_parent, old_index); |
| BookmarkNodeAddedImpl(model, new_parent, new_index); |
| StartThrobbing(); |
| } |
| |
| void BookmarkBarView::BookmarkNodeAdded(BookmarkModel* model, |
| const BookmarkNode* parent, |
| int index) { |
| StopThrobbing(true); |
| BookmarkNodeAddedImpl(model, parent, index); |
| StartThrobbing(); |
| } |
| |
| 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()); |
| AddChildView(index, CreateBookmarkButton(parent->GetChild(index))); |
| UpdateColors(); |
| Layout(); |
| SchedulePaint(); |
| } |
| |
| void BookmarkBarView::BookmarkNodeRemoved(BookmarkModel* model, |
| const BookmarkNode* parent, |
| int old_index, |
| const BookmarkNode* node) { |
| StopThrobbing(true); |
| BookmarkNodeRemovedImpl(model, parent, old_index); |
| StartThrobbing(); |
| } |
| |
| 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, |
| int press_x, |
| int press_y, |
| OSExchangeData* data) { |
| UserMetrics::RecordAction("BookmarkBar_DragButton", profile_); |
| |
| for (int i = 0; i < GetBookmarkButtonCount(); ++i) { |
| if (sender == GetBookmarkButton(i)) { |
| views::TextButton* button = GetBookmarkButton(i); |
| gfx::Canvas canvas(button->width(), button->height(), false); |
| button->Paint(&canvas, true); |
| drag_utils::SetDragImageOnDataObject(canvas, button->width(), |
| button->height(), press_x, |
| press_y, data); |
| WriteDragData(model_->GetBookmarkBarNode()->GetChild(i), data); |
| return; |
| } |
| } |
| NOTREACHED(); |
| } |
| |
| void BookmarkBarView::WriteDragData(const BookmarkNode* node, |
| OSExchangeData* data) { |
| DCHECK(node && data); |
| BookmarkDragData drag_data(node); |
| drag_data.Write(profile_, data); |
| } |
| |
| int BookmarkBarView::GetDragOperations(View* sender, int x, int y) { |
| if (size_animation_->IsAnimating() || |
| (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; |
| } |
| |
| 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, false); |
| 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_->ShowLoginDialog(); |
| 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("ClickedBookmarkBarURLButton", profile_); |
| } |
| |
| void BookmarkBarView::ShowContextMenu(View* source, |
| int x, |
| int y, |
| 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, |
| BookmarkContextMenuController::BOOKMARK_BAR); |
| controller.RunMenuAt(gfx::Point(x, y)); |
| } |
| |
| views::View* BookmarkBarView::CreateBookmarkButton(const BookmarkNode* node) { |
| if (node->is_url()) { |
| BookmarkButton* button = new BookmarkButton(this, |
| node->GetURL(), |
| node->GetTitle(), |
| GetProfile()); |
| ConfigureButton(node, button); |
| return button; |
| } else { |
| views::MenuButton* button = |
| new BookmarkFolderButton(this, node->GetTitle(), this, false); |
| button->SetIcon(GetGroupIcon()); |
| ConfigureButton(node, button); |
| return button; |
| } |
| } |
| |
| void BookmarkBarView::ConfigureButton(const BookmarkNode* node, |
| views::TextButton* button) { |
| button->SetText(node->GetTitle()); |
| button->SetAccessibleName(node->GetTitle()); |
| |
| // 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); |
| bubble_url_ = *(Details<GURL>(details).ptr()); |
| StartThrobbing(); |
| break; |
| |
| case NotificationType::BOOKMARK_BUBBLE_HIDDEN: |
| StopThrobbing(false); |
| bubble_url_ = GURL(); |
| break; |
| |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void BookmarkBarView::ThemeChanged() { |
| 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, false); |
| 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); |
| #if defined(OS_WIN) |
| static DWORD delay = 0; |
| if (!delay && !SystemParametersInfo(SPI_GETMENUSHOWDELAY, 0, &delay, 0)) |
| delay = kShowFolderDropMenuDelay; |
| #else |
| int delay = kShowFolderDropMenuDelay; |
| #endif |
| 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() { |
| DCHECK(!throbbing_view_); |
| |
| if (bubble_url_.is_empty()) |
| return; // Bubble isn't showing; nothing to throb. |
| |
| if (!GetWidget()) |
| return; // We're not showing, don't do anything. |
| |
| const BookmarkNode* node = |
| model_->GetMostRecentlyAddedNodeForURL(bubble_url_); |
| if (!node) |
| return; // Generally shouldn't happen. |
| |
| // Determine which visible button is showing the url (or is an ancestor of |
| // the url). |
| if (node->HasAncestor(model_->GetBookmarkBarNode())) { |
| const BookmarkNode* bbn = model_->GetBookmarkBarNode(); |
| const BookmarkNode* parent_on_bb = node; |
| while (parent_on_bb->GetParent() != bbn) |
| parent_on_bb = parent_on_bb->GetParent(); |
| int index = bbn->IndexOfChild(parent_on_bb); |
| if (index >= GetFirstHiddenNodeIndex()) { |
| // Node is hidden, animate the overflow button. |
| throbbing_view_ = overflow_button_; |
| } else { |
| throbbing_view_ = static_cast<CustomButton*>(GetChildViewAt(index)); |
| } |
| } else { |
| throbbing_view_ = other_bookmarked_button_; |
| } |
| |
| // Use a large number so that the button continues to throb. |
| throbbing_view_->StartThrobbing(std::numeric_limits<int>::max()); |
| } |
| |
| 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); |
| instructions_->SetColor(text_color); |
| 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 y = kTopMargin; |
| int width = View::width() - kRightMargin - kLeftMargin; |
| int height = View::height() - kTopMargin - 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(); |
| const bool should_show_sync_error_button = ShouldShowSyncErrorButton(); |
| if (should_show_sync_error_button) { |
| 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 - kTopMargin, |
| bookmarks_separator_pref.width(), |
| height + kTopMargin + 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 (should_show_sync_error_button) { |
| 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; |
| } |
| |
| // The sync state reported by the profile sync service determines whether or |
| // not the re-login indicator button should be visible. |
| bool BookmarkBarView::ShouldShowSyncErrorButton() { |
| bool show_sync_error_button(false); |
| if (sync_service_ && sync_service_->HasSyncSetupCompleted()) { |
| string16 status_text; |
| string16 link_text; |
| sync_ui_util::MessageType sync_status; |
| sync_status = sync_ui_util::GetStatusLabels( |
| sync_service_, &status_text, &link_text); |
| if (sync_status == sync_ui_util::SYNC_ERROR) { |
| show_sync_error_button = true; |
| } |
| } |
| return show_sync_error_button; |
| } |
| |
| 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; |
| } |