diff options
Diffstat (limited to 'chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.cc')
-rw-r--r-- | chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.cc | 1444 |
1 files changed, 1444 insertions, 0 deletions
diff --git a/chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.cc b/chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.cc new file mode 100644 index 0000000..9b6d7dd --- /dev/null +++ b/chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.cc @@ -0,0 +1,1444 @@ +// 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/gtk/bookmarks/bookmark_bar_gtk.h" + +#include <vector> + +#include "base/metrics/histogram.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/bookmarks/bookmark_node_data.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" +#include "chrome/browser/browser_shutdown.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/browser/metrics/user_metrics.h" +#include "chrome/browser/ntp_background_util.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/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_contents_view.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/gtk/bookmarks/bookmark_menu_controller_gtk.h" +#include "chrome/browser/ui/gtk/bookmarks/bookmark_tree_model.h" +#include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h" +#include "chrome/browser/ui/gtk/browser_window_gtk.h" +#include "chrome/browser/ui/gtk/cairo_cached_surface.h" +#include "chrome/browser/ui/gtk/custom_button.h" +#include "chrome/browser/ui/gtk/gtk_chrome_button.h" +#include "chrome/browser/ui/gtk/gtk_theme_provider.h" +#include "chrome/browser/ui/gtk/gtk_util.h" +#include "chrome/browser/ui/gtk/hover_controller_gtk.h" +#include "chrome/browser/ui/gtk/importer/import_dialog_gtk.h" +#include "chrome/browser/ui/gtk/menu_gtk.h" +#include "chrome/browser/ui/gtk/rounded_window.h" +#include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h" +#include "chrome/browser/ui/gtk/tabstrip_origin_provider.h" +#include "chrome/browser/ui/gtk/view_id_util.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pref_names.h" +#include "grit/app_resources.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "ui/base/animation/slide_animation.h" +#include "ui/base/dragdrop/gtk_dnd_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/canvas_skia_paint.h" +#include "ui/gfx/gtk_util.h" + +namespace { + +// The showing height of the bar. +const int kBookmarkBarHeight = 29; + +// Padding for when the bookmark bar is floating. +const int kTopBottomNTPPadding = 12; +const int kLeftRightNTPPadding = 8; + +// Padding around the bar's content area when the bookmark bar is floating. +const int kNTPPadding = 2; + +// The number of pixels of rounding on the corners of the bookmark bar content +// area when in floating mode. +const int kNTPRoundedness = 3; + +// The height of the bar when it is "hidden". It is usually not completely +// hidden because even when it is closed it forms the bottom few pixels of +// the toolbar. +const int kBookmarkBarMinimumHeight = 3; + +// Left-padding for the instructional text. +const int kInstructionsPadding = 6; + +// Padding around the "Other Bookmarks" button. +const int kOtherBookmarksPaddingHorizontal = 2; +const int kOtherBookmarksPaddingVertical = 1; + +// The targets accepted by the toolbar and folder buttons for DnD. +const int kDestTargetList[] = { ui::CHROME_BOOKMARK_ITEM, + ui::CHROME_NAMED_URL, + ui::TEXT_URI_LIST, + ui::NETSCAPE_URL, + ui::TEXT_PLAIN, -1 }; + +// Acceptable drag actions for the bookmark bar drag destinations. +const GdkDragAction kDragAction = + GdkDragAction(GDK_ACTION_MOVE | GDK_ACTION_COPY); + +void SetToolBarStyle() { + static bool style_was_set = false; + + if (style_was_set) + return; + style_was_set = true; + + gtk_rc_parse_string( + "style \"chrome-bookmark-toolbar\" {" + " xthickness = 0\n" + " ythickness = 0\n" + " GtkWidget::focus-padding = 0\n" + " GtkContainer::border-width = 0\n" + " GtkToolbar::internal-padding = 1\n" + " GtkToolbar::shadow-type = GTK_SHADOW_NONE\n" + "}\n" + "widget \"*chrome-bookmark-toolbar\" style \"chrome-bookmark-toolbar\""); +} + +void RecordAppLaunch(Profile* profile, GURL url) { + // TODO: the ExtensionService should never be NULL, but in some cases it is, + // see bug 73768. After it is resolved, the explicit test can go away. + ExtensionService* service = profile->GetExtensionService(); + if (!service || !service->IsInstalledApp(url)) + return; + + UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram, + extension_misc::APP_LAUNCH_BOOKMARK_BAR, + extension_misc::APP_LAUNCH_BUCKET_BOUNDARY); +} + +} // namespace + +const int BookmarkBarGtk::kBookmarkBarNTPHeight = 57; + +BookmarkBarGtk::BookmarkBarGtk(BrowserWindowGtk* window, + Profile* profile, Browser* browser, + TabstripOriginProvider* tabstrip_origin_provider) + : profile_(NULL), + page_navigator_(NULL), + browser_(browser), + window_(window), + tabstrip_origin_provider_(tabstrip_origin_provider), + model_(NULL), + instructions_(NULL), + sync_service_(NULL), + dragged_node_(NULL), + drag_icon_(NULL), + toolbar_drop_item_(NULL), + theme_provider_(GtkThemeProvider::GetFrom(profile)), + show_instructions_(true), + menu_bar_helper_(this), + floating_(false), + last_allocation_width_(-1), + throbbing_widget_(NULL), + method_factory_(this) { + 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); + } + + Init(profile); + SetProfile(profile); + // Force an update by simulating being in the wrong state. + floating_ = !ShouldBeFloating(); + UpdateFloatingState(); + + registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, + NotificationService::AllSources()); +} + +BookmarkBarGtk::~BookmarkBarGtk() { + RemoveAllBookmarkButtons(); + bookmark_toolbar_.Destroy(); + event_box_.Destroy(); +} + +void BookmarkBarGtk::SetProfile(Profile* profile) { + DCHECK(profile); + if (profile_ == profile) + return; + + RemoveAllBookmarkButtons(); + + profile_ = profile; + + if (model_) + model_->RemoveObserver(this); + + // TODO(erg): Handle extensions + + 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 BookmarkBarGtk::SetPageNavigator(PageNavigator* navigator) { + page_navigator_ = navigator; +} + +void BookmarkBarGtk::Init(Profile* profile) { + event_box_.Own(gtk_event_box_new()); + g_signal_connect(event_box_.get(), "destroy", + G_CALLBACK(&OnEventBoxDestroyThunk), this); + g_signal_connect(event_box_.get(), "button-press-event", + G_CALLBACK(&OnButtonPressedThunk), this); + + ntp_padding_box_ = gtk_alignment_new(0, 0, 1, 1); + gtk_container_add(GTK_CONTAINER(event_box_.get()), ntp_padding_box_); + + paint_box_ = gtk_event_box_new(); + gtk_container_add(GTK_CONTAINER(ntp_padding_box_), paint_box_); + GdkColor paint_box_color = + theme_provider_->GetGdkColor(BrowserThemeProvider::COLOR_TOOLBAR); + gtk_widget_modify_bg(paint_box_, GTK_STATE_NORMAL, &paint_box_color); + gtk_widget_add_events(paint_box_, GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK); + + bookmark_hbox_ = gtk_hbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(paint_box_), bookmark_hbox_); + + instructions_ = gtk_alignment_new(0, 0, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(instructions_), 0, 0, + kInstructionsPadding, 0); + instructions_gtk_.reset(new BookmarkBarInstructionsGtk(this, profile)); + gtk_container_add(GTK_CONTAINER(instructions_), instructions_gtk_->widget()); + gtk_box_pack_start(GTK_BOX(bookmark_hbox_), instructions_, + TRUE, TRUE, 0); + + gtk_drag_dest_set(instructions_, + GtkDestDefaults(GTK_DEST_DEFAULT_DROP | GTK_DEST_DEFAULT_MOTION), + NULL, 0, kDragAction); + ui::SetDestTargetList(instructions_, kDestTargetList); + g_signal_connect(instructions_, "drag-data-received", + G_CALLBACK(&OnDragReceivedThunk), this); + + g_signal_connect(event_box_.get(), "expose-event", + G_CALLBACK(&OnEventBoxExposeThunk), this); + UpdateEventBoxPaintability(); + + bookmark_toolbar_.Own(gtk_toolbar_new()); + SetToolBarStyle(); + gtk_widget_set_name(bookmark_toolbar_.get(), "chrome-bookmark-toolbar"); + gtk_util::SuppressDefaultPainting(bookmark_toolbar_.get()); + g_signal_connect(bookmark_toolbar_.get(), "size-allocate", + G_CALLBACK(&OnToolbarSizeAllocateThunk), this); + gtk_box_pack_start(GTK_BOX(bookmark_hbox_), bookmark_toolbar_.get(), + TRUE, TRUE, 0); + + overflow_button_ = theme_provider_->BuildChromeButton(); + g_object_set_data(G_OBJECT(overflow_button_), "left-align-popup", + reinterpret_cast<void*>(true)); + SetOverflowButtonAppearance(); + ConnectFolderButtonEvents(overflow_button_, false); + gtk_box_pack_start(GTK_BOX(bookmark_hbox_), overflow_button_, + FALSE, FALSE, 0); + + gtk_drag_dest_set(bookmark_toolbar_.get(), GTK_DEST_DEFAULT_DROP, + NULL, 0, kDragAction); + ui::SetDestTargetList(bookmark_toolbar_.get(), kDestTargetList); + g_signal_connect(bookmark_toolbar_.get(), "drag-motion", + G_CALLBACK(&OnToolbarDragMotionThunk), this); + g_signal_connect(bookmark_toolbar_.get(), "drag-leave", + G_CALLBACK(&OnDragLeaveThunk), this); + g_signal_connect(bookmark_toolbar_.get(), "drag-data-received", + G_CALLBACK(&OnDragReceivedThunk), this); + + GtkWidget* vseparator = theme_provider_->CreateToolbarSeparator(); + gtk_box_pack_start(GTK_BOX(bookmark_hbox_), vseparator, + FALSE, FALSE, 0); + + // We pack the button manually (rather than using gtk_button_set_*) so that + // we can have finer control over its label. + other_bookmarks_button_ = theme_provider_->BuildChromeButton(); + ConnectFolderButtonEvents(other_bookmarks_button_, false); + GtkWidget* other_padding = gtk_alignment_new(0, 0, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(other_padding), + kOtherBookmarksPaddingVertical, + kOtherBookmarksPaddingVertical, + kOtherBookmarksPaddingHorizontal, + kOtherBookmarksPaddingHorizontal); + gtk_container_add(GTK_CONTAINER(other_padding), other_bookmarks_button_); + gtk_box_pack_start(GTK_BOX(bookmark_hbox_), other_padding, + FALSE, FALSE, 0); + + sync_error_button_ = theme_provider_->BuildChromeButton(); + gtk_button_set_image( + GTK_BUTTON(sync_error_button_), + gtk_image_new_from_pixbuf( + ResourceBundle::GetSharedInstance().GetPixbufNamed(IDR_WARNING))); + g_signal_connect(sync_error_button_, "button-press-event", + G_CALLBACK(OnSyncErrorButtonPressedThunk), this); + gtk_box_pack_start(GTK_BOX(bookmark_hbox_), sync_error_button_, + FALSE, FALSE, 0); + + gtk_widget_set_size_request(event_box_.get(), -1, kBookmarkBarMinimumHeight); + + slide_animation_.reset(new ui::SlideAnimation(this)); + + ViewIDUtil::SetID(other_bookmarks_button_, VIEW_ID_OTHER_BOOKMARKS); + ViewIDUtil::SetID(widget(), VIEW_ID_BOOKMARK_BAR); + + gtk_widget_show_all(widget()); + gtk_widget_hide(widget()); +} + +void BookmarkBarGtk::Show(bool animate) { + gtk_widget_show_all(widget()); + bool old_floating = floating_; + UpdateFloatingState(); + // TODO(estade): animate the transition between floating and non. + animate = animate && (old_floating == floating_); + if (animate) { + slide_animation_->Show(); + } else { + slide_animation_->Reset(1); + AnimationProgressed(slide_animation_.get()); + } + + // Hide out behind the findbar. This is rather fragile code, it could + // probably be improved. + if (floating_) { + if (theme_provider_->UseGtkTheme()) { + if (GTK_WIDGET_REALIZED(event_box_->parent)) + gdk_window_lower(event_box_->parent->window); + if (GTK_WIDGET_REALIZED(event_box_.get())) + gdk_window_lower(event_box_->window); + } else { // Chromium theme mode. + if (GTK_WIDGET_REALIZED(paint_box_)) { + gdk_window_lower(paint_box_->window); + // The event box won't stay below its children's GdkWindows unless we + // toggle the above-child property here. If the event box doesn't stay + // below its children then events will be routed to it rather than the + // children. + gtk_event_box_set_above_child(GTK_EVENT_BOX(event_box_.get()), TRUE); + gtk_event_box_set_above_child(GTK_EVENT_BOX(event_box_.get()), FALSE); + } + } + } + + if (sync_ui_util::ShouldShowSyncErrorButton(sync_service_)) { + gtk_widget_show(sync_error_button_); + } else { + gtk_widget_hide(sync_error_button_); + } + + // Maybe show the instructions + if (show_instructions_) { + gtk_widget_hide(bookmark_toolbar_.get()); + gtk_widget_show(instructions_); + } else { + gtk_widget_hide(instructions_); + gtk_widget_show(bookmark_toolbar_.get()); + } + + SetChevronState(); +} + +void BookmarkBarGtk::Hide(bool animate) { + UpdateFloatingState(); + + // After coming out of fullscreen, the browser window sets the bookmark bar + // to the "hidden" state, which means we need to show our minimum height. + gtk_widget_show(widget()); + // Sometimes we get called without a matching call to open. If that happens + // then force hide. + if (slide_animation_->IsShowing() && animate) { + slide_animation_->Hide(); + } else { + gtk_widget_hide(bookmark_hbox_); + slide_animation_->Reset(0); + AnimationProgressed(slide_animation_.get()); + } +} + +void BookmarkBarGtk::OnStateChanged() { + if (sync_ui_util::ShouldShowSyncErrorButton(sync_service_)) { + gtk_widget_show(sync_error_button_); + } else { + gtk_widget_hide(sync_error_button_); + } +} + +void BookmarkBarGtk::ShowImportDialog() { + ImportDialogGtk::Show(window_->window(), browser_->profile(), + importer::FAVORITES); +} + +void BookmarkBarGtk::EnterFullscreen() { + if (ShouldBeFloating()) + Show(false); + else + gtk_widget_hide(widget()); +} + +int BookmarkBarGtk::GetHeight() { + return event_box_->allocation.height - kBookmarkBarMinimumHeight; +} + +bool BookmarkBarGtk::IsAnimating() { + return slide_animation_->is_animating(); +} + +bool BookmarkBarGtk::OnNewTabPage() { + return (browser_ && browser_->GetSelectedTabContents() && + browser_->GetSelectedTabContents()->ShouldShowBookmarkBar()); +} + +void BookmarkBarGtk::Loaded(BookmarkModel* model) { + // If |instructions_| has been nulled, we are in the middle of browser + // shutdown. Do nothing. + if (!instructions_) + return; + + RemoveAllBookmarkButtons(); + CreateAllBookmarkButtons(); +} + +void BookmarkBarGtk::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. + if (!browser_shutdown::ShuttingDownWithoutClosingBrowsers()) + NOTREACHED(); + + // Do minimal cleanup, presumably we'll be deleted shortly. + model_->RemoveObserver(this); + model_ = NULL; +} + +void BookmarkBarGtk::BookmarkNodeMoved(BookmarkModel* model, + const BookmarkNode* old_parent, + int old_index, + const BookmarkNode* new_parent, + int new_index) { + const BookmarkNode* node = new_parent->GetChild(new_index); + BookmarkNodeRemoved(model, old_parent, old_index, node); + BookmarkNodeAdded(model, new_parent, new_index); +} + +void BookmarkBarGtk::BookmarkNodeAdded(BookmarkModel* model, + const BookmarkNode* parent, + int index) { + const BookmarkNode* node = parent->GetChild(index); + if (parent != model_->GetBookmarkBarNode()) { + StartThrobbing(node); + return; + } + DCHECK(index >= 0 && index <= GetBookmarkButtonCount()); + + GtkToolItem* item = CreateBookmarkToolItem(node); + gtk_toolbar_insert(GTK_TOOLBAR(bookmark_toolbar_.get()), + item, index); + if (node->is_folder()) + menu_bar_helper_.Add(gtk_bin_get_child(GTK_BIN(item))); + + SetInstructionState(); + SetChevronState(); + + StartThrobbingAfterAllocation(GTK_WIDGET(item)); +} + +void BookmarkBarGtk::BookmarkNodeRemoved(BookmarkModel* model, + const BookmarkNode* parent, + int old_index, + const BookmarkNode* node) { + if (parent != model_->GetBookmarkBarNode()) { + // We only care about nodes on the bookmark bar. + return; + } + DCHECK(old_index >= 0 && old_index < GetBookmarkButtonCount()); + + GtkWidget* to_remove = GTK_WIDGET(gtk_toolbar_get_nth_item( + GTK_TOOLBAR(bookmark_toolbar_.get()), old_index)); + if (node->is_folder()) + menu_bar_helper_.Remove(gtk_bin_get_child(GTK_BIN(to_remove))); + gtk_container_remove(GTK_CONTAINER(bookmark_toolbar_.get()), + to_remove); + + SetInstructionState(); + SetChevronState(); +} + +void BookmarkBarGtk::BookmarkNodeChanged(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(index != -1); + + GtkToolItem* item = gtk_toolbar_get_nth_item( + GTK_TOOLBAR(bookmark_toolbar_.get()), index); + GtkWidget* button = gtk_bin_get_child(GTK_BIN(item)); + bookmark_utils::ConfigureButtonForNode(node, model, button, theme_provider_); + SetChevronState(); +} + +void BookmarkBarGtk::BookmarkNodeFavIconLoaded(BookmarkModel* model, + const BookmarkNode* node) { + BookmarkNodeChanged(model, node); +} + +void BookmarkBarGtk::BookmarkNodeChildrenReordered(BookmarkModel* model, + const BookmarkNode* node) { + if (node != model_->GetBookmarkBarNode()) + return; // We only care about reordering of the bookmark bar node. + + // Purge and rebuild the bar. + RemoveAllBookmarkButtons(); + CreateAllBookmarkButtons(); +} + +void BookmarkBarGtk::CreateAllBookmarkButtons() { + const BookmarkNode* bar = model_->GetBookmarkBarNode(); + DCHECK(bar && model_->other_node()); + + // Create a button for each of the children on the bookmark bar. + for (int i = 0; i < bar->GetChildCount(); ++i) { + const BookmarkNode* node = bar->GetChild(i); + GtkToolItem* item = CreateBookmarkToolItem(node); + gtk_toolbar_insert(GTK_TOOLBAR(bookmark_toolbar_.get()), item, -1); + if (node->is_folder()) + menu_bar_helper_.Add(gtk_bin_get_child(GTK_BIN(item))); + } + + bookmark_utils::ConfigureButtonForNode(model_->other_node(), + model_, other_bookmarks_button_, theme_provider_); + + SetInstructionState(); + SetChevronState(); +} + +void BookmarkBarGtk::SetInstructionState() { + show_instructions_ = (model_->GetBookmarkBarNode()->GetChildCount() == 0); + if (show_instructions_) { + gtk_widget_hide(bookmark_toolbar_.get()); + gtk_widget_show_all(instructions_); + } else { + gtk_widget_hide(instructions_); + gtk_widget_show(bookmark_toolbar_.get()); + } +} + +void BookmarkBarGtk::SetChevronState() { + if (!GTK_WIDGET_VISIBLE(bookmark_hbox_)) + return; + + if (show_instructions_) { + gtk_widget_hide(overflow_button_); + return; + } + + int extra_space = 0; + if (GTK_WIDGET_VISIBLE(overflow_button_)) + extra_space = overflow_button_->allocation.width; + + int overflow_idx = GetFirstHiddenBookmark(extra_space, NULL); + if (overflow_idx == -1) + gtk_widget_hide(overflow_button_); + else + gtk_widget_show_all(overflow_button_); +} + +void BookmarkBarGtk::RemoveAllBookmarkButtons() { + gtk_util::RemoveAllChildren(bookmark_toolbar_.get()); + menu_bar_helper_.Clear(); + menu_bar_helper_.Add(other_bookmarks_button_); + menu_bar_helper_.Add(overflow_button_); +} + +int BookmarkBarGtk::GetBookmarkButtonCount() { + GList* children = gtk_container_get_children( + GTK_CONTAINER(bookmark_toolbar_.get())); + int count = g_list_length(children); + g_list_free(children); + return count; +} + +void BookmarkBarGtk::SetOverflowButtonAppearance() { + GtkWidget* former_child = gtk_bin_get_child(GTK_BIN(overflow_button_)); + if (former_child) + gtk_widget_destroy(former_child); + + GtkWidget* new_child = theme_provider_->UseGtkTheme() ? + gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE) : + gtk_image_new_from_pixbuf(ResourceBundle::GetSharedInstance(). + GetRTLEnabledPixbufNamed(IDR_BOOKMARK_BAR_CHEVRONS)); + + gtk_container_add(GTK_CONTAINER(overflow_button_), new_child); + SetChevronState(); +} + +int BookmarkBarGtk::GetFirstHiddenBookmark( + int extra_space, std::vector<GtkWidget*>* showing_folders) { + int rv = 0; + bool overflow = false; + GList* toolbar_items = + gtk_container_get_children(GTK_CONTAINER(bookmark_toolbar_.get())); + for (GList* iter = toolbar_items; iter; iter = g_list_next(iter)) { + GtkWidget* tool_item = reinterpret_cast<GtkWidget*>(iter->data); + if (gtk_widget_get_direction(tool_item) == GTK_TEXT_DIR_RTL) { + overflow = (tool_item->allocation.x + tool_item->style->xthickness < + bookmark_toolbar_.get()->allocation.x - extra_space); + } else { + overflow = + (tool_item->allocation.x + tool_item->allocation.width + + tool_item->style->xthickness > + bookmark_toolbar_.get()->allocation.width + + bookmark_toolbar_.get()->allocation.x + extra_space); + } + overflow = overflow || tool_item->allocation.x == -1; + + if (overflow) + break; + + if (showing_folders && + model_->GetBookmarkBarNode()->GetChild(rv)->is_folder()) { + showing_folders->push_back(gtk_bin_get_child(GTK_BIN(tool_item))); + } + rv++; + } + + g_list_free(toolbar_items); + + if (!overflow) + return -1; + + return rv; +} + +bool BookmarkBarGtk::ShouldBeFloating() { + return (!IsAlwaysShown() || (window_ && window_->IsFullscreen())) && + window_ && window_->GetDisplayedTabContents() && + window_->GetDisplayedTabContents()->ShouldShowBookmarkBar(); +} + +void BookmarkBarGtk::UpdateFloatingState() { + bool old_floating = floating_; + floating_ = ShouldBeFloating(); + if (floating_ == old_floating) + return; + + if (floating_) { +#if !defined(OS_CHROMEOS) + gtk_event_box_set_visible_window(GTK_EVENT_BOX(paint_box_), TRUE); +#endif + GdkColor stroke_color = theme_provider_->UseGtkTheme() ? + theme_provider_->GetBorderColor() : + theme_provider_->GetGdkColor(BrowserThemeProvider::COLOR_NTP_HEADER); + gtk_util::ActAsRoundedWindow(paint_box_, stroke_color, kNTPRoundedness, + gtk_util::ROUNDED_ALL, gtk_util::BORDER_ALL); + + gtk_alignment_set_padding(GTK_ALIGNMENT(ntp_padding_box_), + kTopBottomNTPPadding, kTopBottomNTPPadding, + kLeftRightNTPPadding, kLeftRightNTPPadding); + gtk_container_set_border_width(GTK_CONTAINER(bookmark_hbox_), kNTPPadding); + } else { + gtk_util::StopActingAsRoundedWindow(paint_box_); +#if !defined(OS_CHROMEOS) + gtk_event_box_set_visible_window(GTK_EVENT_BOX(paint_box_), FALSE); +#endif + gtk_alignment_set_padding(GTK_ALIGNMENT(ntp_padding_box_), 0, 0, 0, 0); + gtk_container_set_border_width(GTK_CONTAINER(bookmark_hbox_), 0); + } + + UpdateEventBoxPaintability(); + // |window_| can be NULL during testing. + if (window_) { + window_->BookmarkBarIsFloating(floating_); + + // Listen for parent size allocations. + if (floating_ && widget()->parent) { + // Only connect once. + if (g_signal_handler_find(widget()->parent, G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, reinterpret_cast<gpointer>(OnParentSizeAllocateThunk), + NULL) == 0) { + g_signal_connect(widget()->parent, "size-allocate", + G_CALLBACK(OnParentSizeAllocateThunk), this); + } + } + } +} + +void BookmarkBarGtk::UpdateEventBoxPaintability() { + gtk_widget_set_app_paintable(event_box_.get(), + !theme_provider_->UseGtkTheme() || floating_); + // When using the GTK+ theme, we need to have the event box be visible so + // buttons don't get a halo color from the background. When using Chromium + // themes, we want to let the background show through the toolbar. + +#if !defined(OS_CHROMEOS) + gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_.get()), + theme_provider_->UseGtkTheme()); +#endif +} + +void BookmarkBarGtk::PaintEventBox() { + gfx::Size tab_contents_size; + if (GetTabContentsSize(&tab_contents_size) && + tab_contents_size != last_tab_contents_size_) { + last_tab_contents_size_ = tab_contents_size; + gtk_widget_queue_draw(event_box_.get()); + } +} + +bool BookmarkBarGtk::GetTabContentsSize(gfx::Size* size) { + Browser* browser = browser_; + if (!browser) { + NOTREACHED(); + return false; + } + TabContents* tab_contents = browser->GetSelectedTabContents(); + if (!tab_contents) { + // It is possible to have a browser but no TabContents while under testing, + // so don't NOTREACHED() and error the program. + return false; + } + if (!tab_contents->view()) { + NOTREACHED(); + return false; + } + *size = tab_contents->view()->GetContainerSize(); + return true; +} + +void BookmarkBarGtk::StartThrobbing(const BookmarkNode* node) { + const BookmarkNode* parent_on_bb = NULL; + for (const BookmarkNode* parent = node; parent; + parent = parent->GetParent()) { + if (parent->GetParent() == model_->GetBookmarkBarNode()) { + parent_on_bb = parent; + break; + } + } + + GtkWidget* widget_to_throb = NULL; + + if (!parent_on_bb) { + // Descendant of "Other Bookmarks". + widget_to_throb = other_bookmarks_button_; + } else { + int hidden = GetFirstHiddenBookmark(0, NULL); + int idx = model_->GetBookmarkBarNode()->IndexOfChild(parent_on_bb); + + if (hidden >= 0 && hidden <= idx) { + widget_to_throb = overflow_button_; + } else { + widget_to_throb = gtk_bin_get_child(GTK_BIN(gtk_toolbar_get_nth_item( + GTK_TOOLBAR(bookmark_toolbar_.get()), idx))); + } + } + + SetThrobbingWidget(widget_to_throb); +} + +void BookmarkBarGtk::SetThrobbingWidget(GtkWidget* widget) { + if (throbbing_widget_) { + HoverControllerGtk* hover_controller = + HoverControllerGtk::GetHoverControllerGtk(throbbing_widget_); + if (hover_controller) + hover_controller->StartThrobbing(0); + + g_signal_handlers_disconnect_by_func( + throbbing_widget_, + reinterpret_cast<gpointer>(OnThrobbingWidgetDestroyThunk), + this); + g_object_unref(throbbing_widget_); + throbbing_widget_ = NULL; + } + + if (widget) { + throbbing_widget_ = widget; + g_object_ref(throbbing_widget_); + g_signal_connect(throbbing_widget_, "destroy", + G_CALLBACK(OnThrobbingWidgetDestroyThunk), this); + + HoverControllerGtk* hover_controller = + HoverControllerGtk::GetHoverControllerGtk(throbbing_widget_); + if (hover_controller) + hover_controller->StartThrobbing(4); + } +} + +void BookmarkBarGtk::OnItemAllocate(GtkWidget* item, + GtkAllocation* allocation) { + // We only want to fire on the item's first allocation. + g_signal_handlers_disconnect_by_func( + item, reinterpret_cast<gpointer>(&OnItemAllocateThunk), this); + + GtkWidget* button = gtk_bin_get_child(GTK_BIN(item)); + const BookmarkNode* node = GetNodeForToolButton(button); + if (node) + StartThrobbing(node); +} + +void BookmarkBarGtk::StartThrobbingAfterAllocation(GtkWidget* item) { + g_signal_connect_after( + item, "size-allocate", G_CALLBACK(OnItemAllocateThunk), this); +} + +bool BookmarkBarGtk::IsAlwaysShown() { + return profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); +} + +void BookmarkBarGtk::AnimationProgressed(const ui::Animation* animation) { + DCHECK_EQ(animation, slide_animation_.get()); + + int max_height = ShouldBeFloating() ? + kBookmarkBarNTPHeight : kBookmarkBarHeight; + gint height = + static_cast<gint>(animation->GetCurrentValue() * + (max_height - kBookmarkBarMinimumHeight)) + + kBookmarkBarMinimumHeight; + gtk_widget_set_size_request(event_box_.get(), -1, height); +} + +void BookmarkBarGtk::AnimationEnded(const ui::Animation* animation) { + DCHECK_EQ(animation, slide_animation_.get()); + + if (!slide_animation_->IsShowing()) { + gtk_widget_hide(bookmark_hbox_); + + // We can be windowless during unit tests. + if (window_) { + // Because of our constant resizing and our toolbar/bookmark bar overlap + // shenanigans, gtk+ gets confused, partially draws parts of the bookmark + // bar into the toolbar and than doesn't queue a redraw to fix it. So do + // it manually by telling the toolbar area to redraw itself. + window_->QueueToolbarRedraw(); + } + } +} + +void BookmarkBarGtk::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::BROWSER_THEME_CHANGED) { + if (model_ && model_->IsLoaded()) { + // Regenerate the bookmark bar with all new objects with their theme + // properties set correctly for the new theme. + RemoveAllBookmarkButtons(); + CreateAllBookmarkButtons(); + } + + UpdateEventBoxPaintability(); + + GdkColor paint_box_color = + theme_provider_->GetGdkColor(BrowserThemeProvider::COLOR_TOOLBAR); + gtk_widget_modify_bg(paint_box_, GTK_STATE_NORMAL, &paint_box_color); + + if (floating_) { + GdkColor stroke_color = theme_provider_->UseGtkTheme() ? + theme_provider_->GetBorderColor() : + theme_provider_->GetGdkColor(BrowserThemeProvider::COLOR_NTP_HEADER); + gtk_util::SetRoundedWindowBorderColor(paint_box_, stroke_color); + } + + SetOverflowButtonAppearance(); + } +} + +GtkWidget* BookmarkBarGtk::CreateBookmarkButton(const BookmarkNode* node) { + GtkWidget* button = theme_provider_->BuildChromeButton(); + bookmark_utils::ConfigureButtonForNode(node, model_, button, theme_provider_); + + // The tool item is also a source for dragging + gtk_drag_source_set(button, GDK_BUTTON1_MASK, NULL, 0, + static_cast<GdkDragAction>(GDK_ACTION_MOVE | GDK_ACTION_COPY)); + int target_mask = bookmark_utils::GetCodeMask(node->is_folder()); + ui::SetSourceTargetListFromCodeMask(button, target_mask); + g_signal_connect(button, "drag-begin", + G_CALLBACK(&OnButtonDragBeginThunk), this); + g_signal_connect(button, "drag-end", + G_CALLBACK(&OnButtonDragEndThunk), this); + g_signal_connect(button, "drag-data-get", + G_CALLBACK(&OnButtonDragGetThunk), this); + // We deliberately don't connect to "drag-data-delete" because the action of + // moving a button will regenerate all the contents of the bookmarks bar + // anyway. + + if (node->is_url()) { + // Connect to 'button-release-event' instead of 'clicked' because we need + // access to the modifier keys and we do different things on each + // button. + g_signal_connect(button, "button-press-event", + G_CALLBACK(OnButtonPressedThunk), this); + g_signal_connect(button, "clicked", + G_CALLBACK(OnClickedThunk), this); + gtk_util::SetButtonTriggersNavigation(button); + } else { + ConnectFolderButtonEvents(button, true); + } + + return button; +} + +GtkToolItem* BookmarkBarGtk::CreateBookmarkToolItem(const BookmarkNode* node) { + GtkWidget* button = CreateBookmarkButton(node); + g_object_set_data(G_OBJECT(button), "left-align-popup", + reinterpret_cast<void*>(true)); + + GtkToolItem* item = gtk_tool_item_new(); + gtk_container_add(GTK_CONTAINER(item), button); + gtk_widget_show_all(GTK_WIDGET(item)); + + return item; +} + +void BookmarkBarGtk::ConnectFolderButtonEvents(GtkWidget* widget, + bool is_tool_item) { + // For toolbar items (i.e. not the overflow button or other bookmarks + // button), we handle motion and highlighting manually. + gtk_drag_dest_set(widget, + is_tool_item ? GTK_DEST_DEFAULT_DROP : + GTK_DEST_DEFAULT_ALL, + NULL, + 0, + kDragAction); + ui::SetDestTargetList(widget, kDestTargetList); + g_signal_connect(widget, "drag-data-received", + G_CALLBACK(&OnDragReceivedThunk), this); + if (is_tool_item) { + g_signal_connect(widget, "drag-motion", + G_CALLBACK(&OnFolderDragMotionThunk), this); + g_signal_connect(widget, "drag-leave", + G_CALLBACK(&OnDragLeaveThunk), this); + } + + g_signal_connect(widget, "button-press-event", + G_CALLBACK(OnButtonPressedThunk), this); + g_signal_connect(widget, "clicked", + G_CALLBACK(OnFolderClickedThunk), this); + + // Accept middle mouse clicking (which opens all). This must be called after + // connecting to "button-press-event" because the handler it attaches stops + // the propagation of that signal. + gtk_util::SetButtonClickableByMouseButtons(widget, true, true, false); +} + +const BookmarkNode* BookmarkBarGtk::GetNodeForToolButton(GtkWidget* widget) { + // First check to see if |button| is special cased. + if (widget == other_bookmarks_button_) + return model_->other_node(); + else if (widget == event_box_.get() || widget == overflow_button_) + return model_->GetBookmarkBarNode(); + + // Search the contents of |bookmark_toolbar_| for the corresponding widget + // and find its index. + GtkWidget* item_to_find = gtk_widget_get_parent(widget); + int index_to_use = -1; + int index = 0; + GList* children = gtk_container_get_children( + GTK_CONTAINER(bookmark_toolbar_.get())); + for (GList* item = children; item; item = item->next, index++) { + if (item->data == item_to_find) { + index_to_use = index; + break; + } + } + g_list_free(children); + + if (index_to_use != -1) + return model_->GetBookmarkBarNode()->GetChild(index_to_use); + + return NULL; +} + +void BookmarkBarGtk::PopupMenuForNode(GtkWidget* sender, + const BookmarkNode* node, + GdkEventButton* event) { + if (!model_->IsLoaded()) { + // Don't do anything if the model isn't loaded. + return; + } + + const BookmarkNode* parent = NULL; + std::vector<const BookmarkNode*> nodes; + if (sender == other_bookmarks_button_) { + nodes.push_back(node); + parent = model_->GetBookmarkBarNode(); + } else if (sender != bookmark_toolbar_.get()) { + nodes.push_back(node); + parent = node->GetParent(); + } else { + parent = model_->GetBookmarkBarNode(); + nodes.push_back(parent); + } + + GtkWindow* window = GTK_WINDOW(gtk_widget_get_toplevel(sender)); + current_context_menu_controller_.reset( + new BookmarkContextMenuController( + window, this, profile_, page_navigator_, parent, nodes)); + current_context_menu_.reset( + new MenuGtk(NULL, current_context_menu_controller_->menu_model())); + current_context_menu_->PopupAsContext( + gfx::Point(event->x_root, event->y_root), + event->time); +} + +gboolean BookmarkBarGtk::OnButtonPressed(GtkWidget* sender, + GdkEventButton* event) { + last_pressed_coordinates_ = gfx::Point(event->x, event->y); + + if (event->button == 3 && GTK_WIDGET_VISIBLE(bookmark_hbox_)) { + const BookmarkNode* node = GetNodeForToolButton(sender); + DCHECK(node); + DCHECK(page_navigator_); + PopupMenuForNode(sender, node, event); + } + + return FALSE; +} + +gboolean BookmarkBarGtk::OnSyncErrorButtonPressed(GtkWidget* sender, + GdkEventButton* event) { + if (sender == sync_error_button_) { + DCHECK(sync_service_ && !sync_service_->IsManaged()); + sync_service_->ShowErrorUI(NULL); + } + + return FALSE; +} + +void BookmarkBarGtk::OnClicked(GtkWidget* sender) { + const BookmarkNode* node = GetNodeForToolButton(sender); + DCHECK(node); + DCHECK(node->is_url()); + DCHECK(page_navigator_); + + RecordAppLaunch(profile_, node->GetURL()); + page_navigator_->OpenURL( + node->GetURL(), GURL(), + gtk_util::DispositionForCurrentButtonPressEvent(), + PageTransition::AUTO_BOOKMARK); + + UserMetrics::RecordAction(UserMetricsAction("ClickedBookmarkBarURLButton"), + profile_); +} + +void BookmarkBarGtk::OnButtonDragBegin(GtkWidget* button, + GdkDragContext* drag_context) { + // The parent tool item might be removed during the drag. Ref it so |button| + // won't get destroyed. + g_object_ref(button->parent); + + const BookmarkNode* node = GetNodeForToolButton(button); + DCHECK(!dragged_node_); + dragged_node_ = node; + DCHECK(dragged_node_); + + drag_icon_ = bookmark_utils::GetDragRepresentationForNode( + node, model_, theme_provider_); + + // We have to jump through some hoops to get the drag icon to line up because + // it is a different size than the button. + GtkRequisition req; + gtk_widget_size_request(drag_icon_, &req); + gfx::Rect button_rect = gtk_util::WidgetBounds(button); + gfx::Point drag_icon_relative = + gfx::Rect(req.width, req.height).CenterPoint().Add( + (last_pressed_coordinates_.Subtract(button_rect.CenterPoint()))); + gtk_drag_set_icon_widget(drag_context, drag_icon_, + drag_icon_relative.x(), + drag_icon_relative.y()); + + // Hide our node, but reserve space for it on the toolbar. + int index = gtk_toolbar_get_item_index(GTK_TOOLBAR(bookmark_toolbar_.get()), + GTK_TOOL_ITEM(button->parent)); + gtk_widget_hide(button); + toolbar_drop_item_ = CreateBookmarkToolItem(dragged_node_); + g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_)); + gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_.get()), + GTK_TOOL_ITEM(toolbar_drop_item_), index); + // Make sure it stays hidden for the duration of the drag. + gtk_widget_set_no_show_all(button, TRUE); +} + +void BookmarkBarGtk::OnButtonDragEnd(GtkWidget* button, + GdkDragContext* drag_context) { + gtk_widget_show(button); + gtk_widget_set_no_show_all(button, FALSE); + + ClearToolbarDropHighlighting(); + + DCHECK(dragged_node_); + dragged_node_ = NULL; + + DCHECK(drag_icon_); + gtk_widget_destroy(drag_icon_); + drag_icon_ = NULL; + + g_object_unref(button->parent); +} + +void BookmarkBarGtk::OnButtonDragGet(GtkWidget* widget, GdkDragContext* context, + GtkSelectionData* selection_data, + guint target_type, guint time) { + const BookmarkNode* node = bookmark_utils::BookmarkNodeForWidget(widget); + bookmark_utils::WriteBookmarkToSelection(node, selection_data, target_type, + profile_); +} + +void BookmarkBarGtk::OnFolderClicked(GtkWidget* sender) { + // Stop its throbbing, if any. + HoverControllerGtk* hover_controller = + HoverControllerGtk::GetHoverControllerGtk(sender); + if (hover_controller) + hover_controller->StartThrobbing(0); + + GdkEvent* event = gtk_get_current_event(); + if (event->button.button == 1) { + PopupForButton(sender); + } else if (event->button.button == 2) { + const BookmarkNode* node = GetNodeForToolButton(sender); + bookmark_utils::OpenAll(window_->GetNativeHandle(), + profile_, page_navigator_, + node, NEW_BACKGROUND_TAB); + } +} + +gboolean BookmarkBarGtk::ItemDraggedOverToolbar(GdkDragContext* context, + int index, + guint time) { + GdkAtom target_type = + gtk_drag_dest_find_target(bookmark_toolbar_.get(), context, NULL); + if (target_type == GDK_NONE) { + // We shouldn't act like a drop target when something that we can't deal + // with is dragged over the toolbar. + return FALSE; + } + + if (!toolbar_drop_item_) { + if (dragged_node_) { + toolbar_drop_item_ = CreateBookmarkToolItem(dragged_node_); + g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_)); + } else { + // Create a fake item the size of other_node(). + // + // TODO(erg): Maybe somehow figure out the real size for the drop target? + toolbar_drop_item_ = + CreateBookmarkToolItem(model_->other_node()); + g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_)); + } + } + + gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_.get()), + GTK_TOOL_ITEM(toolbar_drop_item_), + index); + if (target_type == ui::GetAtomForTarget(ui::CHROME_BOOKMARK_ITEM)) { + gdk_drag_status(context, GDK_ACTION_MOVE, time); + } else { + gdk_drag_status(context, GDK_ACTION_COPY, time); + } + + return TRUE; +} + +gboolean BookmarkBarGtk::OnToolbarDragMotion(GtkWidget* toolbar, + GdkDragContext* context, + gint x, + gint y, + guint time) { + gint index = gtk_toolbar_get_drop_index(GTK_TOOLBAR(toolbar), x, y); + return ItemDraggedOverToolbar(context, index, time); +} + +int BookmarkBarGtk::GetToolbarIndexForDragOverFolder( + GtkWidget* button, gint x) { + int margin = std::min(15, static_cast<int>(0.3 * button->allocation.width)); + if (x > margin && x < (button->allocation.width - margin)) + return -1; + + gint index = gtk_toolbar_get_item_index(GTK_TOOLBAR(bookmark_toolbar_.get()), + GTK_TOOL_ITEM(button->parent)); + if (x > margin) + index++; + return index; +} + +gboolean BookmarkBarGtk::OnFolderDragMotion(GtkWidget* button, + GdkDragContext* context, + gint x, + gint y, + guint time) { + GdkAtom target_type = gtk_drag_dest_find_target(button, context, NULL); + if (target_type == GDK_NONE) + return FALSE; + + int index = GetToolbarIndexForDragOverFolder(button, x); + if (index < 0) { + ClearToolbarDropHighlighting(); + + // Drag is over middle of folder. + gtk_drag_highlight(button); + if (target_type == ui::GetAtomForTarget(ui::CHROME_BOOKMARK_ITEM)) { + gdk_drag_status(context, GDK_ACTION_MOVE, time); + } else { + gdk_drag_status(context, GDK_ACTION_COPY, time); + } + + return TRUE; + } + + // Remove previous highlighting. + gtk_drag_unhighlight(button); + return ItemDraggedOverToolbar(context, index, time); +} + +void BookmarkBarGtk::ClearToolbarDropHighlighting() { + if (toolbar_drop_item_) { + g_object_unref(toolbar_drop_item_); + toolbar_drop_item_ = NULL; + } + + gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_.get()), + NULL, 0); +} + +void BookmarkBarGtk::OnDragLeave(GtkWidget* sender, + GdkDragContext* context, + guint time) { + if (GTK_IS_BUTTON(sender)) + gtk_drag_unhighlight(sender); + + ClearToolbarDropHighlighting(); +} + +void BookmarkBarGtk::OnToolbarSizeAllocate(GtkWidget* widget, + GtkAllocation* allocation) { + if (bookmark_toolbar_.get()->allocation.width == + last_allocation_width_) { + // If the width hasn't changed, then the visibility of the chevron + // doesn't need to change. This check prevents us from getting stuck in a + // loop where allocates are queued indefinitely while the visibility of + // overflow chevron toggles without actual resizes of the toolbar. + return; + } + last_allocation_width_ = bookmark_toolbar_.get()->allocation.width; + + SetChevronState(); +} + +void BookmarkBarGtk::OnDragReceived(GtkWidget* widget, + GdkDragContext* context, + gint x, gint y, + GtkSelectionData* selection_data, + guint target_type, guint time) { + gboolean dnd_success = FALSE; + gboolean delete_selection_data = FALSE; + + const BookmarkNode* dest_node = model_->GetBookmarkBarNode(); + gint index; + if (widget == bookmark_toolbar_.get()) { + index = gtk_toolbar_get_drop_index( + GTK_TOOLBAR(bookmark_toolbar_.get()), x, y); + } else if (widget == instructions_) { + dest_node = model_->GetBookmarkBarNode(); + index = 0; + } else { + index = GetToolbarIndexForDragOverFolder(widget, x); + if (index < 0) { + dest_node = GetNodeForToolButton(widget); + index = dest_node->GetChildCount(); + } + } + + switch (target_type) { + case ui::CHROME_BOOKMARK_ITEM: { + std::vector<const BookmarkNode*> nodes = + bookmark_utils::GetNodesFromSelection(context, selection_data, + target_type, + profile_, + &delete_selection_data, + &dnd_success); + DCHECK(!nodes.empty()); + for (std::vector<const BookmarkNode*>::iterator it = nodes.begin(); + it != nodes.end(); ++it) { + model_->Move(*it, dest_node, index); + index = dest_node->IndexOfChild(*it) + 1; + } + break; + } + + case ui::CHROME_NAMED_URL: { + dnd_success = bookmark_utils::CreateNewBookmarkFromNamedUrl( + selection_data, model_, dest_node, index); + break; + } + + case ui::TEXT_URI_LIST: { + dnd_success = bookmark_utils::CreateNewBookmarksFromURIList( + selection_data, model_, dest_node, index); + break; + } + + case ui::NETSCAPE_URL: { + dnd_success = bookmark_utils::CreateNewBookmarkFromNetscapeURL( + selection_data, model_, dest_node, index); + break; + } + + case ui::TEXT_PLAIN: { + guchar* text = gtk_selection_data_get_text(selection_data); + if (!text) + break; + GURL url(reinterpret_cast<char*>(text)); + g_free(text); + // TODO(estade): It would be nice to head this case off at drag motion, + // so that it doesn't look like we can drag onto the bookmark bar. + if (!url.is_valid()) + break; + string16 title = bookmark_utils::GetNameForURL(url); + model_->AddURL(dest_node, index, title, url); + dnd_success = TRUE; + break; + } + } + + gtk_drag_finish(context, dnd_success, delete_selection_data, time); +} + +gboolean BookmarkBarGtk::OnEventBoxExpose(GtkWidget* widget, + GdkEventExpose* event) { + GtkThemeProvider* theme_provider = theme_provider_; + + // We don't need to render the toolbar image in GTK mode, except when + // detached. + if (theme_provider->UseGtkTheme() && !floating_) + return FALSE; + + if (!floating_) { + cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(widget->window)); + gdk_cairo_rectangle(cr, &event->area); + cairo_clip(cr); + + // Paint the background theme image. + gfx::Point tabstrip_origin = + tabstrip_origin_provider_->GetTabStripOriginForWidget(widget); + gtk_util::DrawThemedToolbarBackground(widget, cr, event, tabstrip_origin, + theme_provider); + + cairo_destroy(cr); + } else { + gfx::Size tab_contents_size; + if (!GetTabContentsSize(&tab_contents_size)) + return FALSE; + gfx::CanvasSkiaPaint canvas(event, true); + + gfx::Rect area = GTK_WIDGET_NO_WINDOW(widget) ? + gfx::Rect(widget->allocation) : + gfx::Rect(0, 0, widget->allocation.width, widget->allocation.height); + NtpBackgroundUtil::PaintBackgroundDetachedMode(theme_provider, &canvas, + area, tab_contents_size.height()); + } + + return FALSE; // Propagate expose to children. +} + +void BookmarkBarGtk::OnEventBoxDestroy(GtkWidget* widget) { + if (model_) + model_->RemoveObserver(this); + + if (sync_service_) + sync_service_->RemoveObserver(this); +} + +void BookmarkBarGtk::OnParentSizeAllocate(GtkWidget* widget, + GtkAllocation* allocation) { + // In floating mode, our layout depends on the size of the tab contents. + // We get the size-allocate signal before the tab contents does, hence we + // need to post a delayed task so we will paint correctly. Note that + // gtk_widget_queue_draw by itself does not work, despite that it claims to + // be asynchronous. + if (floating_) { + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &BookmarkBarGtk::PaintEventBox)); + } +} + +void BookmarkBarGtk::OnThrobbingWidgetDestroy(GtkWidget* widget) { + SetThrobbingWidget(NULL); +} + +// MenuBarHelper::Delegate implementation -------------------------------------- +void BookmarkBarGtk::PopupForButton(GtkWidget* button) { + const BookmarkNode* node = GetNodeForToolButton(button); + DCHECK(node); + DCHECK(page_navigator_); + + int first_hidden = GetFirstHiddenBookmark(0, NULL); + if (first_hidden == -1) { + // No overflow exists: don't show anything for the overflow button. + if (button == overflow_button_) + return; + } else { + // Overflow exists: don't show anything for an overflowed folder button. + if (button != overflow_button_ && button != other_bookmarks_button_ && + node->GetParent()->IndexOfChild(node) >= first_hidden) { + return; + } + } + + current_menu_.reset( + new BookmarkMenuController(browser_, profile_, page_navigator_, + GTK_WINDOW(gtk_widget_get_toplevel(button)), + node, + button == overflow_button_ ? + first_hidden : 0)); + menu_bar_helper_.MenuStartedShowing(button, current_menu_->widget()); + GdkEvent* event = gtk_get_current_event(); + current_menu_->Popup(button, event->button.button, event->button.time); + gdk_event_free(event); +} + +void BookmarkBarGtk::PopupForButtonNextTo(GtkWidget* button, + GtkMenuDirectionType dir) { + const BookmarkNode* relative_node = GetNodeForToolButton(button); + DCHECK(relative_node); + + // Find out the order of the buttons. + std::vector<GtkWidget*> folder_list; + const int first_hidden = GetFirstHiddenBookmark(0, &folder_list); + if (first_hidden != -1) + folder_list.push_back(overflow_button_); + folder_list.push_back(other_bookmarks_button_); + + // Find the position of |button|. + int button_idx = -1; + for (size_t i = 0; i < folder_list.size(); ++i) { + if (folder_list[i] == button) { + button_idx = i; + break; + } + } + DCHECK_NE(button_idx, -1); + + // Find the GtkWidget* for the actual target button. + int shift = dir == GTK_MENU_DIR_PARENT ? -1 : 1; + button_idx = (button_idx + shift + folder_list.size()) % folder_list.size(); + PopupForButton(folder_list[button_idx]); +} + +void BookmarkBarGtk::CloseMenu() { + current_context_menu_->Cancel(); +} |