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