summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.cc')
-rw-r--r--chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.cc1444
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();
+}