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