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