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