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