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