Home | History | Annotate | Download | only in bookmarks
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/bind.h"
     10 #include "base/debug/trace_event.h"
     11 #include "base/metrics/histogram.h"
     12 #include "base/pickle.h"
     13 #include "base/prefs/pref_service.h"
     14 #include "base/strings/utf_string_conversions.h"
     15 #include "chrome/browser/bookmarks/bookmark_model.h"
     16 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
     17 #include "chrome/browser/bookmarks/bookmark_node_data.h"
     18 #include "chrome/browser/bookmarks/bookmark_utils.h"
     19 #include "chrome/browser/browser_shutdown.h"
     20 #include "chrome/browser/chrome_notification_types.h"
     21 #include "chrome/browser/extensions/extension_service.h"
     22 #include "chrome/browser/profiles/profile.h"
     23 #include "chrome/browser/themes/theme_properties.h"
     24 #include "chrome/browser/ui/bookmarks/bookmark_bar_constants.h"
     25 #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
     26 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
     27 #include "chrome/browser/ui/browser.h"
     28 #include "chrome/browser/ui/chrome_pages.h"
     29 #include "chrome/browser/ui/gtk/bookmarks/bookmark_bar_instructions_gtk.h"
     30 #include "chrome/browser/ui/gtk/bookmarks/bookmark_menu_controller_gtk.h"
     31 #include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
     32 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
     33 #include "chrome/browser/ui/gtk/custom_button.h"
     34 #include "chrome/browser/ui/gtk/event_utils.h"
     35 #include "chrome/browser/ui/gtk/gtk_chrome_button.h"
     36 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     37 #include "chrome/browser/ui/gtk/gtk_util.h"
     38 #include "chrome/browser/ui/gtk/hover_controller_gtk.h"
     39 #include "chrome/browser/ui/gtk/menu_gtk.h"
     40 #include "chrome/browser/ui/gtk/rounded_window.h"
     41 #include "chrome/browser/ui/gtk/tabstrip_origin_provider.h"
     42 #include "chrome/browser/ui/gtk/view_id_util.h"
     43 #include "chrome/browser/ui/ntp_background_util.h"
     44 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     45 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
     46 #include "chrome/common/extensions/extension_constants.h"
     47 #include "chrome/common/pref_names.h"
     48 #include "chrome/common/url_constants.h"
     49 #include "content/public/browser/notification_details.h"
     50 #include "content/public/browser/notification_source.h"
     51 #include "content/public/browser/user_metrics.h"
     52 #include "content/public/browser/web_contents.h"
     53 #include "content/public/browser/web_contents_view.h"
     54 #include "grit/generated_resources.h"
     55 #include "grit/theme_resources.h"
     56 #include "grit/ui_resources.h"
     57 #include "ui/base/dragdrop/drag_drop_types.h"
     58 #include "ui/base/dragdrop/gtk_dnd_util.h"
     59 #include "ui/base/gtk/gtk_compat.h"
     60 #include "ui/base/l10n/l10n_util.h"
     61 #include "ui/base/resource/resource_bundle.h"
     62 #include "ui/gfx/canvas_skia_paint.h"
     63 #include "ui/gfx/gtk_util.h"
     64 #include "ui/gfx/image/cairo_cached_surface.h"
     65 #include "ui/gfx/image/image.h"
     66 
     67 using content::PageNavigator;
     68 using content::UserMetricsAction;
     69 using content::WebContents;
     70 
     71 namespace {
     72 
     73 // The showing height of the bar.
     74 const int kBookmarkBarHeight = 29;
     75 
     76 // Padding for when the bookmark bar is detached.
     77 const int kTopBottomNTPPadding = 12;
     78 const int kLeftRightNTPPadding = 8;
     79 
     80 // Padding around the bar's content area when the bookmark bar is detached.
     81 const int kNTPPadding = 2;
     82 
     83 // The number of pixels of rounding on the corners of the bookmark bar content
     84 // area when in detached mode.
     85 const int kNTPRoundedness = 3;
     86 
     87 // The height of the bar when it is "hidden". It is usually not completely
     88 // hidden because even when it is closed it forms the bottom few pixels of
     89 // the toolbar.
     90 const int kBookmarkBarMinimumHeight = 3;
     91 
     92 // Left-padding for the instructional text.
     93 const int kInstructionsPadding = 6;
     94 
     95 // Padding around the "Other Bookmarks" button.
     96 const int kOtherBookmarksPaddingHorizontal = 2;
     97 const int kOtherBookmarksPaddingVertical = 1;
     98 
     99 // The targets accepted by the toolbar and folder buttons for DnD.
    100 const int kDestTargetList[] = { ui::CHROME_BOOKMARK_ITEM,
    101                                 ui::CHROME_NAMED_URL,
    102                                 ui::TEXT_URI_LIST,
    103                                 ui::NETSCAPE_URL,
    104                                 ui::TEXT_PLAIN, -1 };
    105 
    106 // Acceptable drag actions for the bookmark bar drag destinations.
    107 const GdkDragAction kDragAction =
    108     GdkDragAction(GDK_ACTION_MOVE | GDK_ACTION_COPY);
    109 
    110 void SetToolBarStyle() {
    111   static bool style_was_set = false;
    112 
    113   if (style_was_set)
    114     return;
    115   style_was_set = true;
    116 
    117   gtk_rc_parse_string(
    118       "style \"chrome-bookmark-toolbar\" {"
    119       "  xthickness = 0\n"
    120       "  ythickness = 0\n"
    121       "  GtkWidget::focus-padding = 0\n"
    122       "  GtkContainer::border-width = 0\n"
    123       "  GtkToolbar::internal-padding = 1\n"
    124       "  GtkToolbar::shadow-type = GTK_SHADOW_NONE\n"
    125       "}\n"
    126       "widget \"*chrome-bookmark-toolbar\" style \"chrome-bookmark-toolbar\"");
    127 }
    128 
    129 void RecordAppLaunch(Profile* profile, const GURL& url) {
    130   DCHECK(profile->GetExtensionService());
    131   const extensions::Extension* extension =
    132       profile->GetExtensionService()->GetInstalledApp(url);
    133   if (!extension)
    134     return;
    135 
    136   CoreAppLauncherHandler::RecordAppLaunchType(
    137       extension_misc::APP_LAUNCH_BOOKMARK_BAR,
    138       extension->GetType());
    139 }
    140 
    141 }  // namespace
    142 
    143 BookmarkBarGtk::BookmarkBarGtk(BrowserWindowGtk* window,
    144                                Browser* browser,
    145                                TabstripOriginProvider* tabstrip_origin_provider)
    146     : page_navigator_(NULL),
    147       browser_(browser),
    148       window_(window),
    149       tabstrip_origin_provider_(tabstrip_origin_provider),
    150       model_(NULL),
    151       instructions_(NULL),
    152       dragged_node_(NULL),
    153       drag_icon_(NULL),
    154       toolbar_drop_item_(NULL),
    155       theme_service_(GtkThemeService::GetFrom(browser->profile())),
    156       show_instructions_(true),
    157       menu_bar_helper_(this),
    158       slide_animation_(this),
    159       last_allocation_width_(-1),
    160       throbbing_widget_(NULL),
    161       weak_factory_(this),
    162       bookmark_bar_state_(BookmarkBar::DETACHED),
    163       max_height_(0) {
    164   Init();
    165   // Force an update by simulating being in the wrong state.
    166   // BrowserWindowGtk sets our true state after we're created.
    167   SetBookmarkBarState(BookmarkBar::SHOW,
    168                       BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
    169 
    170   registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
    171                  content::Source<ThemeService>(theme_service_));
    172 
    173   apps_shortcut_visible_.Init(
    174       prefs::kShowAppsShortcutInBookmarkBar,
    175       browser_->profile()->GetPrefs(),
    176       base::Bind(&BookmarkBarGtk::OnAppsPageShortcutVisibilityChanged,
    177                  base::Unretained(this)));
    178 
    179   OnAppsPageShortcutVisibilityChanged();
    180 
    181   edit_bookmarks_enabled_.Init(
    182       prefs::kEditBookmarksEnabled,
    183       browser_->profile()->GetPrefs(),
    184       base::Bind(&BookmarkBarGtk::OnEditBookmarksEnabledChanged,
    185                  base::Unretained(this)));
    186 
    187   OnEditBookmarksEnabledChanged();
    188 }
    189 
    190 BookmarkBarGtk::~BookmarkBarGtk() {
    191   RemoveAllButtons();
    192   bookmark_toolbar_.Destroy();
    193   event_box_.Destroy();
    194 }
    195 
    196 void BookmarkBarGtk::SetPageNavigator(PageNavigator* navigator) {
    197   page_navigator_ = navigator;
    198 }
    199 
    200 void BookmarkBarGtk::Init() {
    201   event_box_.Own(gtk_event_box_new());
    202   g_signal_connect(event_box_.get(), "destroy",
    203                    G_CALLBACK(&OnEventBoxDestroyThunk), this);
    204   g_signal_connect(event_box_.get(), "button-press-event",
    205                    G_CALLBACK(&OnButtonPressedThunk), this);
    206 
    207   ntp_padding_box_ = gtk_alignment_new(0, 0, 1, 1);
    208   gtk_container_add(GTK_CONTAINER(event_box_.get()), ntp_padding_box_);
    209 
    210   paint_box_ = gtk_event_box_new();
    211   gtk_container_add(GTK_CONTAINER(ntp_padding_box_), paint_box_);
    212   GdkColor paint_box_color =
    213       theme_service_->GetGdkColor(ThemeProperties::COLOR_TOOLBAR);
    214   gtk_widget_modify_bg(paint_box_, GTK_STATE_NORMAL, &paint_box_color);
    215   gtk_widget_add_events(paint_box_, GDK_POINTER_MOTION_MASK |
    216                                     GDK_BUTTON_PRESS_MASK);
    217 
    218   bookmark_hbox_ = gtk_hbox_new(FALSE, 0);
    219   gtk_container_add(GTK_CONTAINER(paint_box_), bookmark_hbox_);
    220 
    221   apps_shortcut_button_ = theme_service_->BuildChromeButton();
    222   bookmark_utils::ConfigureAppsShortcutButton(apps_shortcut_button_,
    223                                               theme_service_);
    224   g_signal_connect(apps_shortcut_button_, "clicked",
    225                    G_CALLBACK(OnAppsButtonClickedThunk), this);
    226   // Accept middle mouse clicking.
    227   gtk_util::SetButtonClickableByMouseButtons(
    228       apps_shortcut_button_, true, true, false);
    229   gtk_box_pack_start(GTK_BOX(bookmark_hbox_), apps_shortcut_button_,
    230                      FALSE, FALSE, 0);
    231 
    232   instructions_ = gtk_alignment_new(0, 0, 1, 1);
    233   gtk_alignment_set_padding(GTK_ALIGNMENT(instructions_), 0, 0,
    234                             kInstructionsPadding, 0);
    235   Profile* profile = browser_->profile();
    236   instructions_gtk_.reset(new BookmarkBarInstructionsGtk(this, profile));
    237   gtk_container_add(GTK_CONTAINER(instructions_), instructions_gtk_->widget());
    238   gtk_box_pack_start(GTK_BOX(bookmark_hbox_), instructions_,
    239                      TRUE, TRUE, 0);
    240 
    241   gtk_drag_dest_set(instructions_,
    242       GtkDestDefaults(GTK_DEST_DEFAULT_DROP | GTK_DEST_DEFAULT_MOTION),
    243       NULL, 0, kDragAction);
    244   ui::SetDestTargetList(instructions_, kDestTargetList);
    245   g_signal_connect(instructions_, "drag-data-received",
    246                    G_CALLBACK(&OnDragReceivedThunk), this);
    247 
    248   g_signal_connect(event_box_.get(), "expose-event",
    249                    G_CALLBACK(&OnEventBoxExposeThunk), this);
    250   UpdateEventBoxPaintability();
    251 
    252   bookmark_toolbar_.Own(gtk_toolbar_new());
    253   SetToolBarStyle();
    254   gtk_widget_set_name(bookmark_toolbar_.get(), "chrome-bookmark-toolbar");
    255   gtk_util::SuppressDefaultPainting(bookmark_toolbar_.get());
    256   g_signal_connect(bookmark_toolbar_.get(), "size-allocate",
    257                    G_CALLBACK(&OnToolbarSizeAllocateThunk), this);
    258   gtk_box_pack_start(GTK_BOX(bookmark_hbox_), bookmark_toolbar_.get(),
    259                      TRUE, TRUE, 0);
    260 
    261   overflow_button_ = theme_service_->BuildChromeButton();
    262   g_object_set_data(G_OBJECT(overflow_button_), "left-align-popup",
    263                     reinterpret_cast<void*>(true));
    264   SetOverflowButtonAppearance();
    265   ConnectFolderButtonEvents(overflow_button_, false);
    266   gtk_box_pack_start(GTK_BOX(bookmark_hbox_), overflow_button_,
    267                      FALSE, FALSE, 0);
    268 
    269   gtk_drag_dest_set(bookmark_toolbar_.get(), GTK_DEST_DEFAULT_DROP,
    270                     NULL, 0, kDragAction);
    271   ui::SetDestTargetList(bookmark_toolbar_.get(), kDestTargetList);
    272   g_signal_connect(bookmark_toolbar_.get(), "drag-motion",
    273                    G_CALLBACK(&OnToolbarDragMotionThunk), this);
    274   g_signal_connect(bookmark_toolbar_.get(), "drag-leave",
    275                    G_CALLBACK(&OnDragLeaveThunk), this);
    276   g_signal_connect(bookmark_toolbar_.get(), "drag-data-received",
    277                    G_CALLBACK(&OnDragReceivedThunk), this);
    278 
    279   other_bookmarks_separator_ = theme_service_->CreateToolbarSeparator();
    280   gtk_box_pack_start(GTK_BOX(bookmark_hbox_), other_bookmarks_separator_,
    281                      FALSE, FALSE, 0);
    282 
    283   // We pack the button manually (rather than using gtk_button_set_*) so that
    284   // we can have finer control over its label.
    285   other_bookmarks_button_ = theme_service_->BuildChromeButton();
    286   gtk_widget_show_all(other_bookmarks_button_);
    287   ConnectFolderButtonEvents(other_bookmarks_button_, false);
    288   other_padding_ = gtk_alignment_new(0, 0, 1, 1);
    289   gtk_alignment_set_padding(GTK_ALIGNMENT(other_padding_),
    290                             kOtherBookmarksPaddingVertical,
    291                             kOtherBookmarksPaddingVertical,
    292                             kOtherBookmarksPaddingHorizontal,
    293                             kOtherBookmarksPaddingHorizontal);
    294   gtk_container_add(GTK_CONTAINER(other_padding_), other_bookmarks_button_);
    295   gtk_box_pack_start(GTK_BOX(bookmark_hbox_), other_padding_,
    296                      FALSE, FALSE, 0);
    297   gtk_widget_set_no_show_all(other_padding_, TRUE);
    298 
    299   gtk_widget_set_size_request(event_box_.get(), -1, kBookmarkBarMinimumHeight);
    300 
    301   ViewIDUtil::SetID(other_bookmarks_button_, VIEW_ID_OTHER_BOOKMARKS);
    302   ViewIDUtil::SetID(widget(), VIEW_ID_BOOKMARK_BAR);
    303 
    304   gtk_widget_show_all(widget());
    305   gtk_widget_hide(widget());
    306 
    307   AddCoreButtons();
    308   // TODO(erg): Handle extensions
    309   model_ = BookmarkModelFactory::GetForProfile(profile);
    310   model_->AddObserver(this);
    311   if (model_->loaded())
    312     Loaded(model_, false);
    313   // else case: we'll receive notification back from the BookmarkModel when done
    314   // loading, then we'll populate the bar.
    315 }
    316 
    317 void BookmarkBarGtk::SetBookmarkBarState(
    318     BookmarkBar::State state,
    319     BookmarkBar::AnimateChangeType animate_type) {
    320   TRACE_EVENT0("ui::gtk", "BookmarkBarGtk::SetBookmarkBarState");
    321   if (animate_type == BookmarkBar::ANIMATE_STATE_CHANGE &&
    322       (state == BookmarkBar::DETACHED ||
    323        bookmark_bar_state_ == BookmarkBar::DETACHED)) {
    324     // TODO(estade): animate the transition between detached and non or remove
    325     // detached entirely.
    326     animate_type = BookmarkBar::DONT_ANIMATE_STATE_CHANGE;
    327   }
    328   BookmarkBar::State old_state = bookmark_bar_state_;
    329   bookmark_bar_state_ = state;
    330   if (state == BookmarkBar::SHOW || state == BookmarkBar::DETACHED)
    331     Show(old_state, animate_type);
    332   else
    333     Hide(old_state, animate_type);
    334 }
    335 
    336 int BookmarkBarGtk::GetHeight() {
    337   GtkAllocation allocation;
    338   gtk_widget_get_allocation(event_box_.get(), &allocation);
    339   return allocation.height - kBookmarkBarMinimumHeight;
    340 }
    341 
    342 bool BookmarkBarGtk::IsAnimating() {
    343   return slide_animation_.is_animating();
    344 }
    345 
    346 void BookmarkBarGtk::CalculateMaxHeight() {
    347   if (theme_service_->UsingNativeTheme()) {
    348     // Get the requisition of our single child instead of the event box itself
    349     // because the event box probably already has a size request.
    350     GtkRequisition req;
    351     gtk_widget_size_request(ntp_padding_box_, &req);
    352     max_height_ = req.height;
    353   } else {
    354     max_height_ = (bookmark_bar_state_ == BookmarkBar::DETACHED) ?
    355                   chrome::kNTPBookmarkBarHeight : kBookmarkBarHeight;
    356   }
    357 }
    358 
    359 void BookmarkBarGtk::AnimationProgressed(const ui::Animation* animation) {
    360   DCHECK_EQ(animation, &slide_animation_);
    361 
    362   gint height =
    363       static_cast<gint>(animation->GetCurrentValue() *
    364                         (max_height_ - kBookmarkBarMinimumHeight)) +
    365       kBookmarkBarMinimumHeight;
    366   gtk_widget_set_size_request(event_box_.get(), -1, height);
    367 }
    368 
    369 void BookmarkBarGtk::AnimationEnded(const ui::Animation* animation) {
    370   DCHECK_EQ(animation, &slide_animation_);
    371 
    372   if (!slide_animation_.IsShowing()) {
    373     gtk_widget_hide(bookmark_hbox_);
    374 
    375     // We can be windowless during unit tests.
    376     if (window_) {
    377       // Because of our constant resizing and our toolbar/bookmark bar overlap
    378       // shenanigans, gtk+ gets confused, partially draws parts of the bookmark
    379       // bar into the toolbar and than doesn't queue a redraw to fix it. So do
    380       // it manually by telling the toolbar area to redraw itself.
    381       window_->QueueToolbarRedraw();
    382     }
    383   }
    384 }
    385 
    386 // MenuBarHelper::Delegate implementation --------------------------------------
    387 void BookmarkBarGtk::PopupForButton(GtkWidget* button) {
    388   const BookmarkNode* node = GetNodeForToolButton(button);
    389   DCHECK(node);
    390   DCHECK(page_navigator_);
    391 
    392   int first_hidden = GetFirstHiddenBookmark(0, NULL);
    393   if (first_hidden == -1) {
    394     // No overflow exists: don't show anything for the overflow button.
    395     if (button == overflow_button_)
    396       return;
    397   } else {
    398     // Overflow exists: don't show anything for an overflowed folder button.
    399     if (button != overflow_button_ && button != other_bookmarks_button_ &&
    400         node->parent()->GetIndexOf(node) >= first_hidden) {
    401       return;
    402     }
    403   }
    404 
    405   current_menu_.reset(
    406       new BookmarkMenuController(browser_, page_navigator_,
    407           GTK_WINDOW(gtk_widget_get_toplevel(button)),
    408           node,
    409           button == overflow_button_ ? first_hidden : 0));
    410   menu_bar_helper_.MenuStartedShowing(button, current_menu_->widget());
    411   GdkEvent* event = gtk_get_current_event();
    412   current_menu_->Popup(button, event->button.button, event->button.time);
    413   gdk_event_free(event);
    414 }
    415 
    416 void BookmarkBarGtk::PopupForButtonNextTo(GtkWidget* button,
    417                                           GtkMenuDirectionType dir) {
    418   const BookmarkNode* relative_node = GetNodeForToolButton(button);
    419   DCHECK(relative_node);
    420 
    421   // Find out the order of the buttons.
    422   std::vector<GtkWidget*> folder_list;
    423   const int first_hidden = GetFirstHiddenBookmark(0, &folder_list);
    424   if (first_hidden != -1)
    425     folder_list.push_back(overflow_button_);
    426   folder_list.push_back(other_bookmarks_button_);
    427 
    428   // Find the position of |button|.
    429   int button_idx = -1;
    430   for (size_t i = 0; i < folder_list.size(); ++i) {
    431     if (folder_list[i] == button) {
    432       button_idx = i;
    433       break;
    434     }
    435   }
    436   DCHECK_NE(button_idx, -1);
    437 
    438   // Find the GtkWidget* for the actual target button.
    439   int shift = dir == GTK_MENU_DIR_PARENT ? -1 : 1;
    440   button_idx = (button_idx + shift + folder_list.size()) % folder_list.size();
    441   PopupForButton(folder_list[button_idx]);
    442 }
    443 
    444 void BookmarkBarGtk::CloseMenu() {
    445   current_context_menu_->Cancel();
    446 }
    447 
    448 void BookmarkBarGtk::Show(BookmarkBar::State old_state,
    449                           BookmarkBar::AnimateChangeType animate_type) {
    450   gtk_widget_show_all(widget());
    451   UpdateDetachedState(old_state);
    452   CalculateMaxHeight();
    453   if (animate_type == BookmarkBar::ANIMATE_STATE_CHANGE) {
    454     slide_animation_.Show();
    455   } else {
    456     slide_animation_.Reset(1);
    457     AnimationProgressed(&slide_animation_);
    458   }
    459 
    460   if (model_ && model_->loaded())
    461     UpdateOtherBookmarksVisibility();
    462 
    463   // Hide out behind the findbar. This is rather fragile code, it could
    464   // probably be improved.
    465   if (bookmark_bar_state_ == BookmarkBar::DETACHED) {
    466     if (theme_service_->UsingNativeTheme()) {
    467       GtkWidget* parent = gtk_widget_get_parent(event_box_.get());
    468       if (gtk_widget_get_realized(parent))
    469         gdk_window_lower(gtk_widget_get_window(parent));
    470       if (gtk_widget_get_realized(event_box_.get()))
    471         gdk_window_lower(gtk_widget_get_window(event_box_.get()));
    472     } else {  // Chromium theme mode.
    473       if (gtk_widget_get_realized(paint_box_)) {
    474         gdk_window_lower(gtk_widget_get_window(paint_box_));
    475         // The event box won't stay below its children's GdkWindows unless we
    476         // toggle the above-child property here. If the event box doesn't stay
    477         // below its children then events will be routed to it rather than the
    478         // children.
    479         gtk_event_box_set_above_child(GTK_EVENT_BOX(event_box_.get()), TRUE);
    480         gtk_event_box_set_above_child(GTK_EVENT_BOX(event_box_.get()), FALSE);
    481       }
    482     }
    483   }
    484 
    485   // Maybe show the instructions
    486   gtk_widget_set_visible(bookmark_toolbar_.get(), !show_instructions_);
    487   gtk_widget_set_visible(instructions_, show_instructions_);
    488 
    489   SetChevronState();
    490 }
    491 
    492 void BookmarkBarGtk::Hide(BookmarkBar::State old_state,
    493                           BookmarkBar::AnimateChangeType animate_type) {
    494   UpdateDetachedState(old_state);
    495 
    496   // After coming out of fullscreen, the browser window sets the bookmark bar
    497   // to the "hidden" state, which means we need to show our minimum height.
    498   if (!window_->IsFullscreen())
    499     gtk_widget_show(widget());
    500   CalculateMaxHeight();
    501   // Sometimes we get called without a matching call to open. If that happens
    502   // then force hide.
    503   if (slide_animation_.IsShowing() &&
    504       animate_type == BookmarkBar::ANIMATE_STATE_CHANGE) {
    505     slide_animation_.Hide();
    506   } else {
    507     gtk_widget_hide(bookmark_hbox_);
    508     slide_animation_.Reset(0);
    509     AnimationProgressed(&slide_animation_);
    510   }
    511 }
    512 
    513 void BookmarkBarGtk::SetInstructionState() {
    514   if (model_)
    515     show_instructions_ = model_->bookmark_bar_node()->empty();
    516 
    517   gtk_widget_set_visible(bookmark_toolbar_.get(), !show_instructions_);
    518   gtk_widget_set_visible(instructions_, show_instructions_);
    519 }
    520 
    521 void BookmarkBarGtk::SetChevronState() {
    522   if (!gtk_widget_get_visible(bookmark_hbox_))
    523     return;
    524 
    525   if (show_instructions_) {
    526     gtk_widget_hide(overflow_button_);
    527     return;
    528   }
    529 
    530   int extra_space = 0;
    531   if (gtk_widget_get_visible(overflow_button_)) {
    532     GtkAllocation allocation;
    533     gtk_widget_get_allocation(overflow_button_, &allocation);
    534     extra_space = allocation.width;
    535   }
    536 
    537   int overflow_idx = GetFirstHiddenBookmark(extra_space, NULL);
    538   if (overflow_idx == -1)
    539     gtk_widget_hide(overflow_button_);
    540   else
    541     gtk_widget_show_all(overflow_button_);
    542 }
    543 
    544 void BookmarkBarGtk::UpdateOtherBookmarksVisibility() {
    545   bool has_other_children = !model_->other_node()->empty();
    546 
    547   gtk_widget_set_visible(other_padding_, has_other_children);
    548   gtk_widget_set_visible(other_bookmarks_separator_, has_other_children);
    549 }
    550 
    551 void BookmarkBarGtk::RemoveAllButtons() {
    552   gtk_util::RemoveAllChildren(bookmark_toolbar_.get());
    553   menu_bar_helper_.Clear();
    554 }
    555 
    556 void BookmarkBarGtk::AddCoreButtons() {
    557   menu_bar_helper_.Add(other_bookmarks_button_);
    558   menu_bar_helper_.Add(overflow_button_);
    559 }
    560 
    561 void BookmarkBarGtk::ResetButtons() {
    562   RemoveAllButtons();
    563   AddCoreButtons();
    564 
    565   const BookmarkNode* bar = model_->bookmark_bar_node();
    566   DCHECK(bar && model_->other_node());
    567 
    568   // Create a button for each of the children on the bookmark bar.
    569   for (int i = 0; i < bar->child_count(); ++i) {
    570     const BookmarkNode* node = bar->GetChild(i);
    571     GtkToolItem* item = CreateBookmarkToolItem(node);
    572     gtk_toolbar_insert(GTK_TOOLBAR(bookmark_toolbar_.get()), item, -1);
    573     if (node->is_folder())
    574       menu_bar_helper_.Add(gtk_bin_get_child(GTK_BIN(item)));
    575   }
    576 
    577   bookmark_utils::ConfigureButtonForNode(model_->other_node(),
    578       model_, other_bookmarks_button_, theme_service_);
    579 
    580   SetInstructionState();
    581   SetChevronState();
    582 }
    583 
    584 int BookmarkBarGtk::GetBookmarkButtonCount() {
    585   GList* children = gtk_container_get_children(
    586       GTK_CONTAINER(bookmark_toolbar_.get()));
    587   int count = g_list_length(children);
    588   g_list_free(children);
    589   return count;
    590 }
    591 
    592 bookmark_utils::BookmarkLaunchLocation
    593     BookmarkBarGtk::GetBookmarkLaunchLocation() const {
    594   return bookmark_bar_state_ == BookmarkBar::DETACHED ?
    595       bookmark_utils::LAUNCH_DETACHED_BAR :
    596       bookmark_utils::LAUNCH_ATTACHED_BAR;
    597 }
    598 
    599 void BookmarkBarGtk::SetOverflowButtonAppearance() {
    600   GtkWidget* former_child = gtk_bin_get_child(GTK_BIN(overflow_button_));
    601   if (former_child)
    602     gtk_widget_destroy(former_child);
    603 
    604   GtkWidget* new_child;
    605   if (theme_service_->UsingNativeTheme()) {
    606     new_child = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
    607   } else {
    608     const gfx::Image& image = ui::ResourceBundle::GetSharedInstance().
    609         GetNativeImageNamed(IDR_BOOKMARK_BAR_CHEVRONS,
    610                             ui::ResourceBundle::RTL_ENABLED);
    611     new_child = gtk_image_new_from_pixbuf(image.ToGdkPixbuf());
    612   }
    613 
    614   gtk_container_add(GTK_CONTAINER(overflow_button_), new_child);
    615   SetChevronState();
    616 }
    617 
    618 int BookmarkBarGtk::GetFirstHiddenBookmark(int extra_space,
    619     std::vector<GtkWidget*>* showing_folders) {
    620   int rv = 0;
    621   // We're going to keep track of how much width we've used as we move along
    622   // the bookmark bar. If we ever surpass the width of the bookmark bar, we'll
    623   // know that's the first hidden bookmark.
    624   int width_used = 0;
    625   // GTK appears to require one pixel of padding to the side of the first and
    626   // last buttons on the bar.
    627   // TODO(gideonwald): figure out the precise source of these extra two pixels
    628   // and make this calculation more reliable.
    629   GtkAllocation allocation;
    630   gtk_widget_get_allocation(bookmark_toolbar_.get(), &allocation);
    631   int total_width = allocation.width - 2;
    632   bool overflow = false;
    633   GtkRequisition requested_size_;
    634   GList* toolbar_items =
    635       gtk_container_get_children(GTK_CONTAINER(bookmark_toolbar_.get()));
    636   for (GList* iter = toolbar_items; iter; iter = g_list_next(iter)) {
    637     GtkWidget* tool_item = reinterpret_cast<GtkWidget*>(iter->data);
    638     gtk_widget_size_request(tool_item, &requested_size_);
    639     width_used += requested_size_.width;
    640     // |extra_space| is available if we can remove the chevron, which happens
    641     // only if there are no more potential overflow bookmarks after this one.
    642     overflow = width_used > total_width + (g_list_next(iter) ? 0 : extra_space);
    643     if (overflow)
    644       break;
    645 
    646     if (showing_folders &&
    647         model_->bookmark_bar_node()->GetChild(rv)->is_folder()) {
    648       showing_folders->push_back(gtk_bin_get_child(GTK_BIN(tool_item)));
    649     }
    650     rv++;
    651   }
    652 
    653   g_list_free(toolbar_items);
    654 
    655   if (!overflow)
    656     return -1;
    657 
    658   return rv;
    659 }
    660 
    661 void BookmarkBarGtk::UpdateDetachedState(BookmarkBar::State old_state) {
    662   bool old_detached = old_state == BookmarkBar::DETACHED;
    663   bool detached = bookmark_bar_state_ == BookmarkBar::DETACHED;
    664   if (detached == old_detached)
    665     return;
    666 
    667   if (detached) {
    668     gtk_event_box_set_visible_window(GTK_EVENT_BOX(paint_box_), TRUE);
    669     GdkColor stroke_color = theme_service_->UsingNativeTheme() ?
    670         theme_service_->GetBorderColor() :
    671         theme_service_->GetGdkColor(ThemeProperties::COLOR_NTP_HEADER);
    672     gtk_util::ActAsRoundedWindow(paint_box_, stroke_color, kNTPRoundedness,
    673                                  gtk_util::ROUNDED_ALL, gtk_util::BORDER_ALL);
    674 
    675     gtk_alignment_set_padding(GTK_ALIGNMENT(ntp_padding_box_),
    676         kTopBottomNTPPadding, kTopBottomNTPPadding,
    677         kLeftRightNTPPadding, kLeftRightNTPPadding);
    678     gtk_container_set_border_width(GTK_CONTAINER(bookmark_hbox_), kNTPPadding);
    679   } else {
    680     gtk_util::StopActingAsRoundedWindow(paint_box_);
    681     gtk_event_box_set_visible_window(GTK_EVENT_BOX(paint_box_), FALSE);
    682     gtk_alignment_set_padding(GTK_ALIGNMENT(ntp_padding_box_), 0, 0, 0, 0);
    683     gtk_container_set_border_width(GTK_CONTAINER(bookmark_hbox_), 0);
    684   }
    685 
    686   UpdateEventBoxPaintability();
    687   // |window_| can be NULL during testing.
    688   // Listen for parent size allocations. Only connect once.
    689   if (window_ && detached) {
    690     GtkWidget* parent = gtk_widget_get_parent(widget());
    691     if (parent &&
    692         g_signal_handler_find(parent, G_SIGNAL_MATCH_FUNC,
    693             0, 0, NULL, reinterpret_cast<gpointer>(OnParentSizeAllocateThunk),
    694             NULL) == 0) {
    695       g_signal_connect(parent, "size-allocate",
    696                        G_CALLBACK(OnParentSizeAllocateThunk), this);
    697     }
    698   }
    699 }
    700 
    701 void BookmarkBarGtk::UpdateEventBoxPaintability() {
    702   gtk_widget_set_app_paintable(
    703       event_box_.get(),
    704       (!theme_service_->UsingNativeTheme() ||
    705        bookmark_bar_state_ == BookmarkBar::DETACHED));
    706   // When using the GTK+ theme, we need to have the event box be visible so
    707   // buttons don't get a halo color from the background.  When using Chromium
    708   // themes, we want to let the background show through the toolbar.
    709 
    710   gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_.get()),
    711                                    theme_service_->UsingNativeTheme());
    712 }
    713 
    714 void BookmarkBarGtk::PaintEventBox() {
    715   gfx::Size web_contents_size;
    716   if (GetWebContentsSize(&web_contents_size) &&
    717       web_contents_size != last_web_contents_size_) {
    718     last_web_contents_size_ = web_contents_size;
    719     gtk_widget_queue_draw(event_box_.get());
    720   }
    721 }
    722 
    723 bool BookmarkBarGtk::GetWebContentsSize(gfx::Size* size) {
    724   Browser* browser = browser_;
    725   if (!browser) {
    726     NOTREACHED();
    727     return false;
    728   }
    729   WebContents* web_contents =
    730       browser->tab_strip_model()->GetActiveWebContents();
    731   if (!web_contents) {
    732     // It is possible to have a browser but no WebContents while under testing,
    733     // so don't NOTREACHED() and error the program.
    734     return false;
    735   }
    736   if (!web_contents->GetView()) {
    737     NOTREACHED();
    738     return false;
    739   }
    740   *size = web_contents->GetView()->GetContainerSize();
    741   return true;
    742 }
    743 
    744 void BookmarkBarGtk::StartThrobbingAfterAllocation(GtkWidget* item) {
    745   g_signal_connect_after(
    746       item, "size-allocate", G_CALLBACK(OnItemAllocateThunk), this);
    747 }
    748 
    749 void BookmarkBarGtk::OnItemAllocate(GtkWidget* item,
    750                                     GtkAllocation* allocation) {
    751   // We only want to fire on the item's first allocation.
    752   g_signal_handlers_disconnect_by_func(
    753       item, reinterpret_cast<gpointer>(&OnItemAllocateThunk), this);
    754 
    755   GtkWidget* button = gtk_bin_get_child(GTK_BIN(item));
    756   const BookmarkNode* node = GetNodeForToolButton(button);
    757   if (node)
    758     StartThrobbing(node);
    759 }
    760 
    761 void BookmarkBarGtk::StartThrobbing(const BookmarkNode* node) {
    762   const BookmarkNode* parent_on_bb = NULL;
    763   for (const BookmarkNode* parent = node; parent;
    764        parent = parent->parent()) {
    765     if (parent->parent() == model_->bookmark_bar_node()) {
    766       parent_on_bb = parent;
    767       break;
    768     }
    769   }
    770 
    771   GtkWidget* widget_to_throb = NULL;
    772 
    773   if (!parent_on_bb) {
    774     // Descendant of "Other Bookmarks".
    775     widget_to_throb = other_bookmarks_button_;
    776   } else {
    777     int hidden = GetFirstHiddenBookmark(0, NULL);
    778     int idx = model_->bookmark_bar_node()->GetIndexOf(parent_on_bb);
    779 
    780     if (hidden >= 0 && hidden <= idx) {
    781       widget_to_throb = overflow_button_;
    782     } else {
    783       widget_to_throb = gtk_bin_get_child(GTK_BIN(gtk_toolbar_get_nth_item(
    784           GTK_TOOLBAR(bookmark_toolbar_.get()), idx)));
    785     }
    786   }
    787 
    788   SetThrobbingWidget(widget_to_throb);
    789 }
    790 
    791 void BookmarkBarGtk::SetThrobbingWidget(GtkWidget* widget) {
    792   if (throbbing_widget_) {
    793     HoverControllerGtk* hover_controller =
    794         HoverControllerGtk::GetHoverControllerGtk(throbbing_widget_);
    795     if (hover_controller)
    796       hover_controller->StartThrobbing(0);
    797 
    798     g_signal_handlers_disconnect_by_func(
    799         throbbing_widget_,
    800         reinterpret_cast<gpointer>(OnThrobbingWidgetDestroyThunk),
    801         this);
    802     g_object_unref(throbbing_widget_);
    803     throbbing_widget_ = NULL;
    804   }
    805 
    806   if (widget) {
    807     throbbing_widget_ = widget;
    808     g_object_ref(throbbing_widget_);
    809     g_signal_connect(throbbing_widget_, "destroy",
    810                      G_CALLBACK(OnThrobbingWidgetDestroyThunk), this);
    811 
    812     HoverControllerGtk* hover_controller =
    813         HoverControllerGtk::GetHoverControllerGtk(throbbing_widget_);
    814     if (hover_controller)
    815       hover_controller->StartThrobbing(4);
    816   }
    817 }
    818 
    819 gboolean BookmarkBarGtk::ItemDraggedOverToolbar(GdkDragContext* context,
    820                                                 int index,
    821                                                 guint time) {
    822   if (!edit_bookmarks_enabled_.GetValue())
    823     return FALSE;
    824   GdkAtom target_type =
    825       gtk_drag_dest_find_target(bookmark_toolbar_.get(), context, NULL);
    826   if (target_type == GDK_NONE) {
    827     // We shouldn't act like a drop target when something that we can't deal
    828     // with is dragged over the toolbar.
    829     return FALSE;
    830   }
    831 
    832   if (!toolbar_drop_item_) {
    833     if (dragged_node_) {
    834       toolbar_drop_item_ = CreateBookmarkToolItem(dragged_node_);
    835       g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_));
    836     } else {
    837       // Create a fake item the size of other_node().
    838       //
    839       // TODO(erg): Maybe somehow figure out the real size for the drop target?
    840       toolbar_drop_item_ =
    841           CreateBookmarkToolItem(model_->other_node());
    842       g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_));
    843     }
    844   }
    845 
    846   gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_.get()),
    847                                       GTK_TOOL_ITEM(toolbar_drop_item_),
    848                                       index);
    849   if (target_type == ui::GetAtomForTarget(ui::CHROME_BOOKMARK_ITEM)) {
    850     gdk_drag_status(context, GDK_ACTION_MOVE, time);
    851   } else {
    852     gdk_drag_status(context, GDK_ACTION_COPY, time);
    853   }
    854 
    855   return TRUE;
    856 }
    857 
    858 int BookmarkBarGtk::GetToolbarIndexForDragOverFolder(GtkWidget* button,
    859                                                      gint x) {
    860   GtkAllocation allocation;
    861   gtk_widget_get_allocation(button, &allocation);
    862 
    863   int margin = std::min(15, static_cast<int>(0.3 * allocation.width));
    864   if (x > margin && x < (allocation.width - margin))
    865     return -1;
    866 
    867   GtkWidget* parent = gtk_widget_get_parent(button);
    868   gint index = gtk_toolbar_get_item_index(GTK_TOOLBAR(bookmark_toolbar_.get()),
    869                                           GTK_TOOL_ITEM(parent));
    870   if (x > margin)
    871     index++;
    872   return index;
    873 }
    874 
    875 void BookmarkBarGtk::ClearToolbarDropHighlighting() {
    876   if (toolbar_drop_item_) {
    877     g_object_unref(toolbar_drop_item_);
    878     toolbar_drop_item_ = NULL;
    879   }
    880 
    881   gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_.get()),
    882                                       NULL, 0);
    883 }
    884 
    885 void BookmarkBarGtk::Loaded(BookmarkModel* model, bool ids_reassigned) {
    886   // If |instructions_| has been nulled, we are in the middle of browser
    887   // shutdown. Do nothing.
    888   if (!instructions_)
    889     return;
    890 
    891   UpdateOtherBookmarksVisibility();
    892   ResetButtons();
    893 }
    894 
    895 void BookmarkBarGtk::BookmarkModelBeingDeleted(BookmarkModel* model) {
    896   // The bookmark model should never be deleted before us. This code exists
    897   // to check for regressions in shutdown code and not crash.
    898   if (!browser_shutdown::ShuttingDownWithoutClosingBrowsers())
    899     NOTREACHED();
    900 
    901   // Do minimal cleanup, presumably we'll be deleted shortly.
    902   model_->RemoveObserver(this);
    903   model_ = NULL;
    904 }
    905 
    906 void BookmarkBarGtk::BookmarkNodeMoved(BookmarkModel* model,
    907                                        const BookmarkNode* old_parent,
    908                                        int old_index,
    909                                        const BookmarkNode* new_parent,
    910                                        int new_index) {
    911   const BookmarkNode* node = new_parent->GetChild(new_index);
    912   BookmarkNodeRemoved(model, old_parent, old_index, node);
    913   BookmarkNodeAdded(model, new_parent, new_index);
    914 }
    915 
    916 void BookmarkBarGtk::BookmarkNodeAdded(BookmarkModel* model,
    917                                        const BookmarkNode* parent,
    918                                        int index) {
    919   UpdateOtherBookmarksVisibility();
    920 
    921   const BookmarkNode* node = parent->GetChild(index);
    922   if (parent != model_->bookmark_bar_node()) {
    923     StartThrobbing(node);
    924     return;
    925   }
    926   DCHECK(index >= 0 && index <= GetBookmarkButtonCount());
    927 
    928   GtkToolItem* item = CreateBookmarkToolItem(node);
    929   gtk_toolbar_insert(GTK_TOOLBAR(bookmark_toolbar_.get()),
    930                      item, index);
    931   if (node->is_folder())
    932     menu_bar_helper_.Add(gtk_bin_get_child(GTK_BIN(item)));
    933 
    934   SetInstructionState();
    935   SetChevronState();
    936 
    937   StartThrobbingAfterAllocation(GTK_WIDGET(item));
    938 }
    939 
    940 void BookmarkBarGtk::BookmarkNodeRemoved(BookmarkModel* model,
    941                                          const BookmarkNode* parent,
    942                                          int old_index,
    943                                          const BookmarkNode* node) {
    944   UpdateOtherBookmarksVisibility();
    945 
    946   if (parent != model_->bookmark_bar_node()) {
    947     // We only care about nodes on the bookmark bar.
    948     return;
    949   }
    950   DCHECK(old_index >= 0 && old_index < GetBookmarkButtonCount());
    951 
    952   GtkWidget* to_remove = GTK_WIDGET(gtk_toolbar_get_nth_item(
    953       GTK_TOOLBAR(bookmark_toolbar_.get()), old_index));
    954   if (node->is_folder())
    955     menu_bar_helper_.Remove(gtk_bin_get_child(GTK_BIN(to_remove)));
    956   gtk_container_remove(GTK_CONTAINER(bookmark_toolbar_.get()),
    957                        to_remove);
    958 
    959   SetInstructionState();
    960   SetChevronState();
    961 }
    962 
    963 void BookmarkBarGtk::BookmarkAllNodesRemoved(BookmarkModel* model) {
    964   UpdateOtherBookmarksVisibility();
    965   ResetButtons();
    966 }
    967 
    968 void BookmarkBarGtk::BookmarkNodeChanged(BookmarkModel* model,
    969                                          const BookmarkNode* node) {
    970   if (node->parent() != model_->bookmark_bar_node()) {
    971     // We only care about nodes on the bookmark bar.
    972     return;
    973   }
    974   int index = model_->bookmark_bar_node()->GetIndexOf(node);
    975   DCHECK(index != -1);
    976 
    977   GtkToolItem* item = gtk_toolbar_get_nth_item(
    978       GTK_TOOLBAR(bookmark_toolbar_.get()), index);
    979   GtkWidget* button = gtk_bin_get_child(GTK_BIN(item));
    980   bookmark_utils::ConfigureButtonForNode(node, model, button, theme_service_);
    981   SetChevronState();
    982 }
    983 
    984 void BookmarkBarGtk::BookmarkNodeFaviconChanged(BookmarkModel* model,
    985                                                 const BookmarkNode* node) {
    986   BookmarkNodeChanged(model, node);
    987 }
    988 
    989 void BookmarkBarGtk::BookmarkNodeChildrenReordered(BookmarkModel* model,
    990                                                    const BookmarkNode* node) {
    991   if (node != model_->bookmark_bar_node())
    992     return;  // We only care about reordering of the bookmark bar node.
    993 
    994   ResetButtons();
    995 }
    996 
    997 void BookmarkBarGtk::Observe(int type,
    998                              const content::NotificationSource& source,
    999                              const content::NotificationDetails& details) {
   1000   if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) {
   1001     if (model_ && model_->loaded()) {
   1002       // Regenerate the bookmark bar with all new objects with their theme
   1003       // properties set correctly for the new theme.
   1004       ResetButtons();
   1005     }
   1006 
   1007     // Resize the bookmark bar since the target height may have changed.
   1008     CalculateMaxHeight();
   1009     AnimationProgressed(&slide_animation_);
   1010 
   1011     UpdateEventBoxPaintability();
   1012 
   1013     GdkColor paint_box_color =
   1014         theme_service_->GetGdkColor(ThemeProperties::COLOR_TOOLBAR);
   1015     gtk_widget_modify_bg(paint_box_, GTK_STATE_NORMAL, &paint_box_color);
   1016 
   1017     if (bookmark_bar_state_ == BookmarkBar::DETACHED) {
   1018       GdkColor stroke_color = theme_service_->UsingNativeTheme() ?
   1019           theme_service_->GetBorderColor() :
   1020           theme_service_->GetGdkColor(ThemeProperties::COLOR_NTP_HEADER);
   1021       gtk_util::SetRoundedWindowBorderColor(paint_box_, stroke_color);
   1022     }
   1023 
   1024     SetOverflowButtonAppearance();
   1025   }
   1026 }
   1027 
   1028 GtkWidget* BookmarkBarGtk::CreateBookmarkButton(const BookmarkNode* node) {
   1029   GtkWidget* button = theme_service_->BuildChromeButton();
   1030   bookmark_utils::ConfigureButtonForNode(node, model_, button, theme_service_);
   1031 
   1032   // The tool item is also a source for dragging
   1033   gtk_drag_source_set(button, GDK_BUTTON1_MASK, NULL, 0,
   1034       static_cast<GdkDragAction>(GDK_ACTION_MOVE | GDK_ACTION_COPY));
   1035   int target_mask = bookmark_utils::GetCodeMask(node->is_folder());
   1036   ui::SetSourceTargetListFromCodeMask(button, target_mask);
   1037   g_signal_connect(button, "drag-begin",
   1038                    G_CALLBACK(&OnButtonDragBeginThunk), this);
   1039   g_signal_connect(button, "drag-end",
   1040                    G_CALLBACK(&OnButtonDragEndThunk), this);
   1041   g_signal_connect(button, "drag-data-get",
   1042                    G_CALLBACK(&OnButtonDragGetThunk), this);
   1043   // We deliberately don't connect to "drag-data-delete" because the action of
   1044   // moving a button will regenerate all the contents of the bookmarks bar
   1045   // anyway.
   1046 
   1047   if (node->is_url()) {
   1048     // Connect to 'button-release-event' instead of 'clicked' because we need
   1049     // access to the modifier keys and we do different things on each
   1050     // button.
   1051     g_signal_connect(button, "button-press-event",
   1052                      G_CALLBACK(OnButtonPressedThunk), this);
   1053     g_signal_connect(button, "clicked",
   1054                      G_CALLBACK(OnClickedThunk), this);
   1055     gtk_util::SetButtonTriggersNavigation(button);
   1056   } else {
   1057     ConnectFolderButtonEvents(button, true);
   1058   }
   1059 
   1060   return button;
   1061 }
   1062 
   1063 GtkToolItem* BookmarkBarGtk::CreateBookmarkToolItem(const BookmarkNode* node) {
   1064   GtkWidget* button = CreateBookmarkButton(node);
   1065   g_object_set_data(G_OBJECT(button), "left-align-popup",
   1066                     reinterpret_cast<void*>(true));
   1067 
   1068   GtkToolItem* item = gtk_tool_item_new();
   1069   gtk_container_add(GTK_CONTAINER(item), button);
   1070   gtk_widget_show_all(GTK_WIDGET(item));
   1071 
   1072   return item;
   1073 }
   1074 
   1075 void BookmarkBarGtk::ConnectFolderButtonEvents(GtkWidget* widget,
   1076                                                bool is_tool_item) {
   1077   // For toolbar items (i.e. not the overflow button or other bookmarks
   1078   // button), we handle motion and highlighting manually.
   1079   gtk_drag_dest_set(widget,
   1080                     is_tool_item ? GTK_DEST_DEFAULT_DROP :
   1081                                    GTK_DEST_DEFAULT_ALL,
   1082                     NULL,
   1083                     0,
   1084                     kDragAction);
   1085   ui::SetDestTargetList(widget, kDestTargetList);
   1086   g_signal_connect(widget, "drag-data-received",
   1087                    G_CALLBACK(&OnDragReceivedThunk), this);
   1088   if (is_tool_item) {
   1089     g_signal_connect(widget, "drag-motion",
   1090                      G_CALLBACK(&OnFolderDragMotionThunk), this);
   1091     g_signal_connect(widget, "drag-leave",
   1092                      G_CALLBACK(&OnDragLeaveThunk), this);
   1093   }
   1094 
   1095   g_signal_connect(widget, "button-press-event",
   1096                    G_CALLBACK(OnButtonPressedThunk), this);
   1097   g_signal_connect(widget, "clicked",
   1098                    G_CALLBACK(OnFolderClickedThunk), this);
   1099 
   1100   // Accept middle mouse clicking (which opens all). This must be called after
   1101   // connecting to "button-press-event" because the handler it attaches stops
   1102   // the propagation of that signal.
   1103   gtk_util::SetButtonClickableByMouseButtons(widget, true, true, false);
   1104 }
   1105 
   1106 const BookmarkNode* BookmarkBarGtk::GetNodeForToolButton(GtkWidget* widget) {
   1107   // First check to see if |button| is special cased.
   1108   if (widget == other_bookmarks_button_)
   1109     return model_->other_node();
   1110   else if (widget == event_box_.get() || widget == overflow_button_)
   1111     return model_->bookmark_bar_node();
   1112 
   1113   // Search the contents of |bookmark_toolbar_| for the corresponding widget
   1114   // and find its index.
   1115   GtkWidget* item_to_find = gtk_widget_get_parent(widget);
   1116   int index_to_use = -1;
   1117   int index = 0;
   1118   GList* children = gtk_container_get_children(
   1119       GTK_CONTAINER(bookmark_toolbar_.get()));
   1120   for (GList* item = children; item; item = item->next, index++) {
   1121     if (item->data == item_to_find) {
   1122       index_to_use = index;
   1123       break;
   1124     }
   1125   }
   1126   g_list_free(children);
   1127 
   1128   if (index_to_use != -1)
   1129     return model_->bookmark_bar_node()->GetChild(index_to_use);
   1130 
   1131   return NULL;
   1132 }
   1133 
   1134 void BookmarkBarGtk::PopupMenuForNode(GtkWidget* sender,
   1135                                       const BookmarkNode* node,
   1136                                       GdkEventButton* event) {
   1137   if (!model_->loaded()) {
   1138     // Don't do anything if the model isn't loaded.
   1139     return;
   1140   }
   1141 
   1142   const BookmarkNode* parent = NULL;
   1143   std::vector<const BookmarkNode*> nodes;
   1144   if (sender == other_bookmarks_button_) {
   1145     nodes.push_back(node);
   1146     parent = model_->bookmark_bar_node();
   1147   } else if (sender != bookmark_toolbar_.get()) {
   1148     nodes.push_back(node);
   1149     parent = node->parent();
   1150   } else {
   1151     parent = model_->bookmark_bar_node();
   1152     nodes.push_back(parent);
   1153   }
   1154 
   1155   GtkWindow* window = GTK_WINDOW(gtk_widget_get_toplevel(sender));
   1156   current_context_menu_controller_.reset(
   1157       new BookmarkContextMenuController(
   1158           window, this, browser_, browser_->profile(), page_navigator_, parent,
   1159           nodes));
   1160   current_context_menu_.reset(
   1161       new MenuGtk(NULL, current_context_menu_controller_->menu_model()));
   1162   current_context_menu_->PopupAsContext(
   1163       gfx::Point(event->x_root, event->y_root),
   1164       event->time);
   1165 }
   1166 
   1167 gboolean BookmarkBarGtk::OnButtonPressed(GtkWidget* sender,
   1168                                          GdkEventButton* event) {
   1169   last_pressed_coordinates_ = gfx::Point(event->x, event->y);
   1170 
   1171   if (event->button == 3 && gtk_widget_get_visible(bookmark_hbox_)) {
   1172     const BookmarkNode* node = GetNodeForToolButton(sender);
   1173     DCHECK(node);
   1174     DCHECK(page_navigator_);
   1175     PopupMenuForNode(sender, node, event);
   1176   }
   1177 
   1178   return FALSE;
   1179 }
   1180 
   1181 void BookmarkBarGtk::OnClicked(GtkWidget* sender) {
   1182   const BookmarkNode* node = GetNodeForToolButton(sender);
   1183   DCHECK(node);
   1184   DCHECK(node->is_url());
   1185   DCHECK(page_navigator_);
   1186 
   1187   RecordAppLaunch(browser_->profile(), node->url());
   1188   chrome::OpenAll(window_->GetNativeWindow(), page_navigator_, node,
   1189                   event_utils::DispositionForCurrentButtonPressEvent(),
   1190                   browser_->profile());
   1191 
   1192   bookmark_utils::RecordBookmarkLaunch(GetBookmarkLaunchLocation());
   1193 }
   1194 
   1195 void BookmarkBarGtk::OnButtonDragBegin(GtkWidget* button,
   1196                                        GdkDragContext* drag_context) {
   1197   GtkWidget* button_parent = gtk_widget_get_parent(button);
   1198 
   1199   // The parent tool item might be removed during the drag. Ref it so |button|
   1200   // won't get destroyed.
   1201   g_object_ref(button_parent);
   1202 
   1203   const BookmarkNode* node = GetNodeForToolButton(button);
   1204   DCHECK(!dragged_node_);
   1205   dragged_node_ = node;
   1206   DCHECK(dragged_node_);
   1207 
   1208   drag_icon_ = bookmark_utils::GetDragRepresentationForNode(
   1209       node, model_, theme_service_);
   1210 
   1211   // We have to jump through some hoops to get the drag icon to line up because
   1212   // it is a different size than the button.
   1213   GtkRequisition req;
   1214   gtk_widget_size_request(drag_icon_, &req);
   1215   gfx::Rect button_rect = gtk_util::WidgetBounds(button);
   1216   gfx::Point drag_icon_relative =
   1217       gfx::Rect(req.width, req.height).CenterPoint() +
   1218       (last_pressed_coordinates_ - button_rect.CenterPoint());
   1219   gtk_drag_set_icon_widget(drag_context, drag_icon_,
   1220                            drag_icon_relative.x(),
   1221                            drag_icon_relative.y());
   1222 
   1223   // Hide our node, but reserve space for it on the toolbar.
   1224   int index = gtk_toolbar_get_item_index(GTK_TOOLBAR(bookmark_toolbar_.get()),
   1225                                          GTK_TOOL_ITEM(button_parent));
   1226   gtk_widget_hide(button);
   1227   toolbar_drop_item_ = CreateBookmarkToolItem(dragged_node_);
   1228   g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_));
   1229   gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_.get()),
   1230                                       GTK_TOOL_ITEM(toolbar_drop_item_), index);
   1231   // Make sure it stays hidden for the duration of the drag.
   1232   gtk_widget_set_no_show_all(button, TRUE);
   1233 }
   1234 
   1235 void BookmarkBarGtk::OnButtonDragEnd(GtkWidget* button,
   1236                                      GdkDragContext* drag_context) {
   1237   gtk_widget_show(button);
   1238   gtk_widget_set_no_show_all(button, FALSE);
   1239 
   1240   ClearToolbarDropHighlighting();
   1241 
   1242   DCHECK(dragged_node_);
   1243   dragged_node_ = NULL;
   1244 
   1245   DCHECK(drag_icon_);
   1246   gtk_widget_destroy(drag_icon_);
   1247   drag_icon_ = NULL;
   1248 
   1249   g_object_unref(gtk_widget_get_parent(button));
   1250 }
   1251 
   1252 void BookmarkBarGtk::OnButtonDragGet(GtkWidget* widget,
   1253                                      GdkDragContext* context,
   1254                                      GtkSelectionData* selection_data,
   1255                                      guint target_type,
   1256                                      guint time) {
   1257   const BookmarkNode* node = bookmark_utils::BookmarkNodeForWidget(widget);
   1258   bookmark_utils::WriteBookmarkToSelection(node, selection_data, target_type,
   1259                                            browser_->profile());
   1260 }
   1261 
   1262 void BookmarkBarGtk::OnAppsButtonClicked(GtkWidget* sender) {
   1263   content::OpenURLParams params(
   1264       GURL(chrome::kChromeUIAppsURL),
   1265       content::Referrer(),
   1266       event_utils::DispositionForCurrentButtonPressEvent(),
   1267       content::PAGE_TRANSITION_AUTO_BOOKMARK,
   1268       false);
   1269   browser_->OpenURL(params);
   1270   bookmark_utils::RecordAppsPageOpen(GetBookmarkLaunchLocation());
   1271 }
   1272 
   1273 void BookmarkBarGtk::OnFolderClicked(GtkWidget* sender) {
   1274   // Stop its throbbing, if any.
   1275   HoverControllerGtk* hover_controller =
   1276       HoverControllerGtk::GetHoverControllerGtk(sender);
   1277   if (hover_controller)
   1278     hover_controller->StartThrobbing(0);
   1279 
   1280   GdkEvent* event = gtk_get_current_event();
   1281   if (event->button.button == 1 ||
   1282       (event->button.button == 2 && sender == overflow_button_)) {
   1283     bookmark_utils::RecordBookmarkFolderOpen(GetBookmarkLaunchLocation());
   1284     PopupForButton(sender);
   1285   } else if (event->button.button == 2) {
   1286     const BookmarkNode* node = GetNodeForToolButton(sender);
   1287     chrome::OpenAll(window_->GetNativeWindow(), page_navigator_, node,
   1288                     NEW_BACKGROUND_TAB, browser_->profile());
   1289   }
   1290   gdk_event_free(event);
   1291 }
   1292 
   1293 gboolean BookmarkBarGtk::OnToolbarDragMotion(GtkWidget* toolbar,
   1294                                              GdkDragContext* context,
   1295                                              gint x,
   1296                                              gint y,
   1297                                              guint time) {
   1298   gint index = gtk_toolbar_get_drop_index(GTK_TOOLBAR(toolbar), x, y);
   1299   return ItemDraggedOverToolbar(context, index, time);
   1300 }
   1301 
   1302 void BookmarkBarGtk::OnToolbarSizeAllocate(GtkWidget* widget,
   1303                                            GtkAllocation* allocation) {
   1304   if (allocation->width == last_allocation_width_) {
   1305     // If the width hasn't changed, then the visibility of the chevron
   1306     // doesn't need to change. This check prevents us from getting stuck in a
   1307     // loop where allocates are queued indefinitely while the visibility of
   1308     // overflow chevron toggles without actual resizes of the toolbar.
   1309     return;
   1310   }
   1311   last_allocation_width_ = allocation->width;
   1312 
   1313   SetChevronState();
   1314 }
   1315 
   1316 void BookmarkBarGtk::OnDragReceived(GtkWidget* widget,
   1317                                     GdkDragContext* context,
   1318                                     gint x, gint y,
   1319                                     GtkSelectionData* selection_data,
   1320                                     guint target_type, guint time) {
   1321   if (!edit_bookmarks_enabled_.GetValue()) {
   1322     gtk_drag_finish(context, FALSE, FALSE, time);
   1323     return;
   1324   }
   1325 
   1326   gboolean dnd_success = FALSE;
   1327   gboolean delete_selection_data = FALSE;
   1328 
   1329   const BookmarkNode* dest_node = model_->bookmark_bar_node();
   1330   gint index;
   1331   if (widget == bookmark_toolbar_.get()) {
   1332     index = gtk_toolbar_get_drop_index(
   1333       GTK_TOOLBAR(bookmark_toolbar_.get()), x, y);
   1334   } else if (widget == instructions_) {
   1335     dest_node = model_->bookmark_bar_node();
   1336     index = 0;
   1337   } else {
   1338     index = GetToolbarIndexForDragOverFolder(widget, x);
   1339     if (index < 0) {
   1340       dest_node = GetNodeForToolButton(widget);
   1341       index = dest_node->child_count();
   1342     }
   1343   }
   1344 
   1345   switch (target_type) {
   1346     case ui::CHROME_BOOKMARK_ITEM: {
   1347       gint length = gtk_selection_data_get_length(selection_data);
   1348       Pickle pickle(reinterpret_cast<const char*>(
   1349           gtk_selection_data_get_data(selection_data)), length);
   1350       BookmarkNodeData drag_data;
   1351       if (drag_data.ReadFromPickle(&pickle)) {
   1352         dnd_success = chrome::DropBookmarks(browser_->profile(),
   1353             drag_data, dest_node, index) != ui::DragDropTypes::DRAG_NONE;
   1354       }
   1355       break;
   1356     }
   1357 
   1358     case ui::CHROME_NAMED_URL: {
   1359       dnd_success = bookmark_utils::CreateNewBookmarkFromNamedUrl(
   1360           selection_data, model_, dest_node, index);
   1361       break;
   1362     }
   1363 
   1364     case ui::TEXT_URI_LIST: {
   1365       dnd_success = bookmark_utils::CreateNewBookmarksFromURIList(
   1366           selection_data, model_, dest_node, index);
   1367       break;
   1368     }
   1369 
   1370     case ui::NETSCAPE_URL: {
   1371       dnd_success = bookmark_utils::CreateNewBookmarkFromNetscapeURL(
   1372           selection_data, model_, dest_node, index);
   1373       break;
   1374     }
   1375 
   1376     case ui::TEXT_PLAIN: {
   1377       guchar* text = gtk_selection_data_get_text(selection_data);
   1378       if (!text)
   1379         break;
   1380       GURL url(reinterpret_cast<char*>(text));
   1381       g_free(text);
   1382       // TODO(estade): It would be nice to head this case off at drag motion,
   1383       // so that it doesn't look like we can drag onto the bookmark bar.
   1384       if (!url.is_valid())
   1385         break;
   1386       string16 title = bookmark_utils::GetNameForURL(url);
   1387       model_->AddURL(dest_node, index, title, url);
   1388       dnd_success = TRUE;
   1389       break;
   1390     }
   1391   }
   1392 
   1393   gtk_drag_finish(context, dnd_success, delete_selection_data, time);
   1394 }
   1395 
   1396 void BookmarkBarGtk::OnDragLeave(GtkWidget* sender,
   1397                                  GdkDragContext* context,
   1398                                  guint time) {
   1399   if (GTK_IS_BUTTON(sender))
   1400     gtk_drag_unhighlight(sender);
   1401 
   1402   ClearToolbarDropHighlighting();
   1403 }
   1404 
   1405 gboolean BookmarkBarGtk::OnFolderDragMotion(GtkWidget* button,
   1406                                             GdkDragContext* context,
   1407                                             gint x,
   1408                                             gint y,
   1409                                             guint time) {
   1410   if (!edit_bookmarks_enabled_.GetValue())
   1411     return FALSE;
   1412   GdkAtom target_type = gtk_drag_dest_find_target(button, context, NULL);
   1413   if (target_type == GDK_NONE)
   1414     return FALSE;
   1415 
   1416   int index = GetToolbarIndexForDragOverFolder(button, x);
   1417   if (index < 0) {
   1418     ClearToolbarDropHighlighting();
   1419 
   1420     // Drag is over middle of folder.
   1421     gtk_drag_highlight(button);
   1422     if (target_type == ui::GetAtomForTarget(ui::CHROME_BOOKMARK_ITEM)) {
   1423       gdk_drag_status(context, GDK_ACTION_MOVE, time);
   1424     } else {
   1425       gdk_drag_status(context, GDK_ACTION_COPY, time);
   1426     }
   1427 
   1428     return TRUE;
   1429   }
   1430 
   1431   // Remove previous highlighting.
   1432   gtk_drag_unhighlight(button);
   1433   return ItemDraggedOverToolbar(context, index, time);
   1434 }
   1435 
   1436 gboolean BookmarkBarGtk::OnEventBoxExpose(GtkWidget* widget,
   1437                                           GdkEventExpose* event) {
   1438   TRACE_EVENT0("ui::gtk", "BookmarkBarGtk::OnEventBoxExpose");
   1439   GtkThemeService* theme_provider = theme_service_;
   1440 
   1441   // We don't need to render the toolbar image in GTK mode, except when
   1442   // detached.
   1443   if (theme_provider->UsingNativeTheme() &&
   1444       bookmark_bar_state_ != BookmarkBar::DETACHED)
   1445     return FALSE;
   1446 
   1447   if (bookmark_bar_state_ != BookmarkBar::DETACHED) {
   1448     cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
   1449     gdk_cairo_rectangle(cr, &event->area);
   1450     cairo_clip(cr);
   1451 
   1452     // Paint the background theme image.
   1453     gfx::Point tabstrip_origin =
   1454         tabstrip_origin_provider_->GetTabStripOriginForWidget(widget);
   1455     gtk_util::DrawThemedToolbarBackground(widget, cr, event, tabstrip_origin,
   1456                                           theme_provider);
   1457 
   1458     cairo_destroy(cr);
   1459   } else {
   1460     gfx::Size web_contents_size;
   1461     if (!GetWebContentsSize(&web_contents_size))
   1462       return FALSE;
   1463     gfx::CanvasSkiaPaint canvas(event, true);
   1464 
   1465     GtkAllocation allocation;
   1466     gtk_widget_get_allocation(widget, &allocation);
   1467 
   1468     gfx::Rect area = gtk_widget_get_has_window(widget) ?
   1469                      gfx::Rect(0, 0, allocation.width, allocation.height) :
   1470                      gfx::Rect(allocation);
   1471     NtpBackgroundUtil::PaintBackgroundDetachedMode(theme_provider, &canvas,
   1472         area, web_contents_size.height());
   1473   }
   1474 
   1475   return FALSE;  // Propagate expose to children.
   1476 }
   1477 
   1478 void BookmarkBarGtk::OnEventBoxDestroy(GtkWidget* widget) {
   1479   if (model_)
   1480     model_->RemoveObserver(this);
   1481 }
   1482 
   1483 void BookmarkBarGtk::OnParentSizeAllocate(GtkWidget* widget,
   1484                                           GtkAllocation* allocation) {
   1485   // In detached mode, our layout depends on the size of the tab contents.
   1486   // We get the size-allocate signal before the tab contents does, hence we
   1487   // need to post a delayed task so we will paint correctly. Note that
   1488   // gtk_widget_queue_draw by itself does not work, despite that it claims to
   1489   // be asynchronous.
   1490   if (bookmark_bar_state_ == BookmarkBar::DETACHED) {
   1491     base::MessageLoop::current()->PostTask(
   1492         FROM_HERE,
   1493         base::Bind(&BookmarkBarGtk::PaintEventBox, weak_factory_.GetWeakPtr()));
   1494   }
   1495 }
   1496 
   1497 void BookmarkBarGtk::OnThrobbingWidgetDestroy(GtkWidget* widget) {
   1498   SetThrobbingWidget(NULL);
   1499 }
   1500 
   1501 void BookmarkBarGtk::ShowImportDialog() {
   1502   chrome::ShowImportDialog(browser_);
   1503 }
   1504 
   1505 void BookmarkBarGtk::OnAppsPageShortcutVisibilityChanged() {
   1506   const bool visible =
   1507       chrome::ShouldShowAppsShortcutInBookmarkBar(browser_->profile());
   1508   gtk_widget_set_visible(apps_shortcut_button_, visible);
   1509   gtk_widget_set_no_show_all(apps_shortcut_button_, !visible);
   1510 }
   1511 
   1512 void BookmarkBarGtk::OnEditBookmarksEnabledChanged() {
   1513   GtkDestDefaults dest_defaults =
   1514       *edit_bookmarks_enabled_ ? GTK_DEST_DEFAULT_ALL :
   1515                                  GTK_DEST_DEFAULT_DROP;
   1516   gtk_drag_dest_set(overflow_button_, dest_defaults, NULL, 0, kDragAction);
   1517   gtk_drag_dest_set(other_bookmarks_button_, dest_defaults,
   1518                     NULL, 0, kDragAction);
   1519   ui::SetDestTargetList(overflow_button_, kDestTargetList);
   1520   ui::SetDestTargetList(other_bookmarks_button_, kDestTargetList);
   1521 }
   1522