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.
5 #include "chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.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"
66 using base::UserMetricsAction;
67 using content::PageNavigator;
68 using content::WebContents;
72 // Padding for when the bookmark bar is detached.
73 const int kTopBottomNTPPadding = 12;
74 const int kLeftRightNTPPadding = 8;
76 // Padding around the bar's content area when the bookmark bar is detached.
77 const int kNTPPadding = 2;
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;
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
86 const int kBookmarkBarMinimumHeight = 3;
88 // Left-padding for the instructional text.
89 const int kInstructionsPadding = 6;
91 // Padding around the "Other Bookmarks" button.
92 const int kOtherBookmarksPaddingHorizontal = 2;
93 const int kOtherBookmarksPaddingVertical = 1;
95 // The targets accepted by the toolbar and folder buttons for DnD.
96 const int kDestTargetList[] = { ui::CHROME_BOOKMARK_ITEM,
100 ui::TEXT_PLAIN, -1 };
102 // Acceptable drag actions for the bookmark bar drag destinations.
103 const GdkDragAction kDragAction =
104 GdkDragAction(GDK_ACTION_MOVE | GDK_ACTION_COPY);
106 void SetToolBarStyle() {
107 static bool style_was_set = false;
111 style_was_set = true;
114 "style \"chrome-bookmark-toolbar\" {"
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"
122 "widget \"*chrome-bookmark-toolbar\" style \"chrome-bookmark-toolbar\"");
125 void RecordAppLaunch(Profile* profile, const GURL& url) {
126 DCHECK(profile->GetExtensionService());
127 const extensions::Extension* extension =
128 profile->GetExtensionService()->GetInstalledApp(url);
132 CoreAppLauncherHandler::RecordAppLaunchType(
133 extension_misc::APP_LAUNCH_BOOKMARK_BAR,
134 extension->GetType());
139 BookmarkBarGtk::BookmarkBarGtk(BrowserWindowGtk* window,
141 TabstripOriginProvider* tabstrip_origin_provider)
142 : page_navigator_(NULL),
145 tabstrip_origin_provider_(tabstrip_origin_provider),
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),
159 weak_factory_(this) {
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);
166 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
167 content::Source<ThemeService>(theme_service_));
169 apps_shortcut_visible_.Init(
170 prefs::kShowAppsShortcutInBookmarkBar,
171 browser_->profile()->GetPrefs(),
172 base::Bind(&BookmarkBarGtk::OnAppsPageShortcutVisibilityChanged,
173 base::Unretained(this)));
175 OnAppsPageShortcutVisibilityChanged();
177 edit_bookmarks_enabled_.Init(
178 prefs::kEditBookmarksEnabled,
179 browser_->profile()->GetPrefs(),
180 base::Bind(&BookmarkBarGtk::OnEditBookmarksEnabledChanged,
181 base::Unretained(this)));
183 OnEditBookmarksEnabledChanged();
186 BookmarkBarGtk::~BookmarkBarGtk() {
188 bookmark_toolbar_.Destroy();
189 event_box_.Destroy();
192 void BookmarkBarGtk::SetPageNavigator(PageNavigator* navigator) {
193 page_navigator_ = navigator;
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);
203 ntp_padding_box_ = gtk_alignment_new(0, 0, 1, 1);
204 gtk_container_add(GTK_CONTAINER(event_box_.get()), ntp_padding_box_);
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);
214 bookmark_hbox_ = gtk_hbox_new(FALSE, 0);
215 gtk_container_add(GTK_CONTAINER(paint_box_), bookmark_hbox_);
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_,
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_,
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);
243 g_signal_connect(event_box_.get(), "expose-event",
244 G_CALLBACK(&OnEventBoxExposeThunk), this);
245 UpdateEventBoxPaintability();
247 bookmark_toolbar_.Own(gtk_toolbar_new());
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(),
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_,
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);
274 other_bookmarks_separator_ = theme_service_->CreateToolbarSeparator();
275 gtk_box_pack_start(GTK_BOX(bookmark_hbox_), other_bookmarks_separator_,
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_,
292 gtk_widget_set_no_show_all(other_padding_, TRUE);
294 gtk_widget_set_size_request(event_box_.get(), -1, kBookmarkBarMinimumHeight);
296 ViewIDUtil::SetID(other_bookmarks_button_, VIEW_ID_OTHER_BOOKMARKS);
297 ViewIDUtil::SetID(widget(), VIEW_ID_BOOKMARK_BAR);
299 gtk_widget_show_all(widget());
300 gtk_widget_hide(widget());
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.
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;
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);
328 Hide(old_state, animate_type);
331 int BookmarkBarGtk::GetHeight() {
332 GtkAllocation allocation;
333 gtk_widget_get_allocation(event_box_.get(), &allocation);
334 return allocation.height - kBookmarkBarMinimumHeight;
337 bool BookmarkBarGtk::IsAnimating() {
338 return slide_animation_.is_animating();
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.
346 gtk_widget_size_request(ntp_padding_box_, &req);
347 max_height_ = req.height;
349 max_height_ = (bookmark_bar_state_ == BookmarkBar::DETACHED) ?
350 chrome::kNTPBookmarkBarHeight : chrome::kBookmarkBarHeight;
354 void BookmarkBarGtk::AnimationProgressed(const gfx::Animation* animation) {
355 DCHECK_EQ(animation, &slide_animation_);
358 static_cast<gint>(animation->GetCurrentValue() *
359 (max_height_ - kBookmarkBarMinimumHeight)) +
360 kBookmarkBarMinimumHeight;
361 gtk_widget_set_size_request(event_box_.get(), -1, height);
364 void BookmarkBarGtk::AnimationEnded(const gfx::Animation* animation) {
365 DCHECK_EQ(animation, &slide_animation_);
367 if (!slide_animation_.IsShowing()) {
368 gtk_widget_hide(bookmark_hbox_);
370 // We can be windowless during unit tests.
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();
381 // MenuBarHelper::Delegate implementation --------------------------------------
382 void BookmarkBarGtk::PopupForButton(GtkWidget* button) {
383 const BookmarkNode* node = GetNodeForToolButton(button);
385 DCHECK(page_navigator_);
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_)
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) {
401 new BookmarkMenuController(browser_, page_navigator_,
402 GTK_WINDOW(gtk_widget_get_toplevel(button)),
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);
411 void BookmarkBarGtk::PopupForButtonNextTo(GtkWidget* button,
412 GtkMenuDirectionType dir) {
413 const BookmarkNode* relative_node = GetNodeForToolButton(button);
414 DCHECK(relative_node);
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_);
422 if (!model_->other_node()->empty())
423 folder_list.push_back(other_bookmarks_button_);
425 // Find the position of |button|.
427 for (size_t i = 0; i < folder_list.size(); ++i) {
428 if (folder_list[i] == button) {
433 DCHECK_NE(button_idx, -1);
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]);
441 void BookmarkBarGtk::CloseMenu() {
442 current_context_menu_->Cancel();
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();
453 slide_animation_.Reset(1);
454 AnimationProgressed(&slide_animation_);
457 if (model_ && model_->loaded())
458 UpdateOtherBookmarksVisibility();
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
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);
482 // Maybe show the instructions
483 gtk_widget_set_visible(bookmark_toolbar_.get(), !show_instructions_);
484 gtk_widget_set_visible(instructions_, show_instructions_);
489 void BookmarkBarGtk::Hide(BookmarkBar::State old_state,
490 BookmarkBar::AnimateChangeType animate_type) {
491 UpdateDetachedState(old_state);
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
500 if (slide_animation_.IsShowing() &&
501 animate_type == BookmarkBar::ANIMATE_STATE_CHANGE) {
502 slide_animation_.Hide();
504 gtk_widget_hide(bookmark_hbox_);
505 slide_animation_.Reset(0);
506 AnimationProgressed(&slide_animation_);
510 void BookmarkBarGtk::SetInstructionState() {
512 show_instructions_ = model_->bookmark_bar_node()->empty();
514 gtk_widget_set_visible(bookmark_toolbar_.get(), !show_instructions_);
515 gtk_widget_set_visible(instructions_, show_instructions_);
518 void BookmarkBarGtk::SetChevronState() {
519 if (!gtk_widget_get_visible(bookmark_hbox_))
522 if (show_instructions_) {
523 gtk_widget_hide(overflow_button_);
528 if (gtk_widget_get_visible(overflow_button_)) {
529 GtkAllocation allocation;
530 gtk_widget_get_allocation(overflow_button_, &allocation);
531 extra_space = allocation.width;
534 int overflow_idx = GetFirstHiddenBookmark(extra_space, NULL);
535 if (overflow_idx == -1)
536 gtk_widget_hide(overflow_button_);
538 gtk_widget_show_all(overflow_button_);
541 void BookmarkBarGtk::UpdateOtherBookmarksVisibility() {
542 bool has_other_children = !model_->other_node()->empty();
544 gtk_widget_set_visible(other_padding_, has_other_children);
545 gtk_widget_set_visible(other_bookmarks_separator_, has_other_children);
548 void BookmarkBarGtk::RemoveAllButtons() {
549 gtk_util::RemoveAllChildren(bookmark_toolbar_.get());
550 menu_bar_helper_.Clear();
553 void BookmarkBarGtk::AddCoreButtons() {
554 menu_bar_helper_.Add(other_bookmarks_button_);
555 menu_bar_helper_.Add(overflow_button_);
558 void BookmarkBarGtk::ResetButtons() {
562 const BookmarkNode* bar = model_->bookmark_bar_node();
563 DCHECK(bar && model_->other_node());
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)));
574 ConfigureButtonForNode(
575 model_->other_node(), model_, other_bookmarks_button_, theme_service_);
577 SetInstructionState();
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);
589 BookmarkLaunchLocation BookmarkBarGtk::GetBookmarkLaunchLocation() const {
590 return bookmark_bar_state_ == BookmarkBar::DETACHED ?
591 BOOKMARK_LAUNCH_LOCATION_DETACHED_BAR :
592 BOOKMARK_LAUNCH_LOCATION_ATTACHED_BAR;
595 void BookmarkBarGtk::SetOverflowButtonAppearance() {
596 GtkWidget* former_child = gtk_bin_get_child(GTK_BIN(overflow_button_));
598 gtk_widget_destroy(former_child);
600 GtkWidget* new_child;
601 if (theme_service_->UsingNativeTheme()) {
602 new_child = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
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());
610 gtk_container_add(GTK_CONTAINER(overflow_button_), new_child);
614 int BookmarkBarGtk::GetFirstHiddenBookmark(int extra_space,
615 std::vector<GtkWidget*>* showing_folders) {
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.
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);
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)));
649 g_list_free(toolbar_items);
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)
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);
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);
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);
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());
688 g_signal_handler_find(parent, G_SIGNAL_MATCH_FUNC,
689 0, 0, NULL, reinterpret_cast<gpointer>(OnParentSizeAllocateThunk),
691 g_signal_connect(parent, "size-allocate",
692 G_CALLBACK(OnParentSizeAllocateThunk), this);
697 void BookmarkBarGtk::UpdateEventBoxPaintability() {
698 gtk_widget_set_app_paintable(
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.
706 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_.get()),
707 theme_service_->UsingNativeTheme());
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());
719 bool BookmarkBarGtk::GetWebContentsSize(gfx::Size* size) {
720 Browser* browser = browser_;
725 WebContents* web_contents =
726 browser->tab_strip_model()->GetActiveWebContents();
728 // It is possible to have a browser but no WebContents while under testing,
729 // so don't NOTREACHED() and error the program.
732 if (!web_contents->GetView()) {
736 *size = web_contents->GetView()->GetContainerSize();
740 void BookmarkBarGtk::StartThrobbingAfterAllocation(GtkWidget* item) {
741 g_signal_connect_after(
742 item, "size-allocate", G_CALLBACK(OnItemAllocateThunk), this);
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);
751 GtkWidget* button = gtk_bin_get_child(GTK_BIN(item));
752 const BookmarkNode* node = GetNodeForToolButton(button);
754 StartThrobbing(node);
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;
767 GtkWidget* widget_to_throb = NULL;
770 // Descendant of "Other Bookmarks".
771 widget_to_throb = other_bookmarks_button_;
773 int hidden = GetFirstHiddenBookmark(0, NULL);
774 int idx = model_->bookmark_bar_node()->GetIndexOf(parent_on_bb);
776 if (hidden >= 0 && hidden <= idx) {
777 widget_to_throb = overflow_button_;
779 widget_to_throb = gtk_bin_get_child(GTK_BIN(gtk_toolbar_get_nth_item(
780 GTK_TOOLBAR(bookmark_toolbar_.get()), idx)));
784 SetThrobbingWidget(widget_to_throb);
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);
794 g_signal_handlers_disconnect_by_func(
796 reinterpret_cast<gpointer>(OnThrobbingWidgetDestroyThunk),
798 g_object_unref(throbbing_widget_);
799 throbbing_widget_ = NULL;
803 throbbing_widget_ = widget;
804 g_object_ref(throbbing_widget_);
805 g_signal_connect(throbbing_widget_, "destroy",
806 G_CALLBACK(OnThrobbingWidgetDestroyThunk), this);
808 HoverControllerGtk* hover_controller =
809 HoverControllerGtk::GetHoverControllerGtk(throbbing_widget_);
810 if (hover_controller)
811 hover_controller->StartThrobbing(4);
815 gboolean BookmarkBarGtk::ItemDraggedOverToolbar(GdkDragContext* context,
818 if (!edit_bookmarks_enabled_.GetValue())
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.
828 if (!toolbar_drop_item_) {
830 toolbar_drop_item_ = CreateBookmarkToolItem(dragged_node_);
831 g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_));
833 // Create a fake item the size of other_node().
835 // TODO(erg): Maybe somehow figure out the real size for the drop target?
837 CreateBookmarkToolItem(model_->other_node());
838 g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_));
842 gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_.get()),
843 GTK_TOOL_ITEM(toolbar_drop_item_),
845 if (target_type == ui::GetAtomForTarget(ui::CHROME_BOOKMARK_ITEM)) {
846 gdk_drag_status(context, GDK_ACTION_MOVE, time);
848 gdk_drag_status(context, GDK_ACTION_COPY, time);
854 int BookmarkBarGtk::GetToolbarIndexForDragOverFolder(GtkWidget* button,
856 GtkAllocation allocation;
857 gtk_widget_get_allocation(button, &allocation);
859 int margin = std::min(15, static_cast<int>(0.3 * allocation.width));
860 if (x > margin && x < (allocation.width - margin))
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));
871 void BookmarkBarGtk::ClearToolbarDropHighlighting() {
872 if (toolbar_drop_item_) {
873 g_object_unref(toolbar_drop_item_);
874 toolbar_drop_item_ = NULL;
877 gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_.get()),
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.
888 UpdateOtherBookmarksVisibility();
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.
897 // Do minimal cleanup, presumably we'll be deleted shortly.
898 model_->RemoveObserver(this);
902 void BookmarkBarGtk::BookmarkNodeMoved(BookmarkModel* model,
903 const BookmarkNode* old_parent,
905 const BookmarkNode* new_parent,
907 const BookmarkNode* node = new_parent->GetChild(new_index);
908 BookmarkNodeRemoved(model, old_parent, old_index, node);
909 BookmarkNodeAdded(model, new_parent, new_index);
912 void BookmarkBarGtk::BookmarkNodeAdded(BookmarkModel* model,
913 const BookmarkNode* parent,
915 UpdateOtherBookmarksVisibility();
917 const BookmarkNode* node = parent->GetChild(index);
918 if (parent != model_->bookmark_bar_node()) {
919 StartThrobbing(node);
922 DCHECK(index >= 0 && index <= GetBookmarkButtonCount());
924 GtkToolItem* item = CreateBookmarkToolItem(node);
925 gtk_toolbar_insert(GTK_TOOLBAR(bookmark_toolbar_.get()),
927 if (node->is_folder())
928 menu_bar_helper_.Add(gtk_bin_get_child(GTK_BIN(item)));
930 SetInstructionState();
933 StartThrobbingAfterAllocation(GTK_WIDGET(item));
936 void BookmarkBarGtk::BookmarkNodeRemoved(BookmarkModel* model,
937 const BookmarkNode* parent,
939 const BookmarkNode* node) {
940 UpdateOtherBookmarksVisibility();
942 if (parent != model_->bookmark_bar_node()) {
943 // We only care about nodes on the bookmark bar.
946 DCHECK(old_index >= 0 && old_index < GetBookmarkButtonCount());
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()),
955 SetInstructionState();
959 void BookmarkBarGtk::BookmarkAllNodesRemoved(BookmarkModel* model) {
960 UpdateOtherBookmarksVisibility();
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.
970 int index = model_->bookmark_bar_node()->GetIndexOf(node);
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_);
980 void BookmarkBarGtk::BookmarkNodeFaviconChanged(BookmarkModel* model,
981 const BookmarkNode* node) {
982 BookmarkNodeChanged(model, node);
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.
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.
1003 // Resize the bookmark bar since the target height may have changed.
1004 CalculateMaxHeight();
1005 AnimationProgressed(&slide_animation_);
1007 UpdateEventBoxPaintability();
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);
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);
1020 SetOverflowButtonAppearance();
1024 GtkWidget* BookmarkBarGtk::CreateBookmarkButton(const BookmarkNode* node) {
1025 GtkWidget* button = theme_service_->BuildChromeButton();
1026 ConfigureButtonForNode(node, model_, button, theme_service_);
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
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
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);
1053 ConnectFolderButtonEvents(button, true);
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));
1064 GtkToolItem* item = gtk_tool_item_new();
1065 gtk_container_add(GTK_CONTAINER(item), button);
1066 gtk_widget_show_all(GTK_WIDGET(item));
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,
1081 ui::SetDestTargetList(widget, kDestTargetList);
1082 g_signal_connect(widget, "drag-data-received",
1083 G_CALLBACK(&OnDragReceivedThunk), this);
1085 g_signal_connect(widget, "drag-motion",
1086 G_CALLBACK(&OnFolderDragMotionThunk), this);
1087 g_signal_connect(widget, "drag-leave",
1088 G_CALLBACK(&OnDragLeaveThunk), this);
1091 g_signal_connect(widget, "button-press-event",
1092 G_CALLBACK(OnButtonPressedThunk), this);
1093 g_signal_connect(widget, "clicked",
1094 G_CALLBACK(OnFolderClickedThunk), this);
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);
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();
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;
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;
1122 g_list_free(children);
1124 if (index_to_use != -1)
1125 return model_->bookmark_bar_node()->GetChild(index_to_use);
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.
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();
1147 parent = model_->bookmark_bar_node();
1148 nodes.push_back(parent);
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,
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),
1163 gboolean BookmarkBarGtk::OnButtonPressed(GtkWidget* sender,
1164 GdkEventButton* event) {
1165 last_pressed_coordinates_ = gfx::Point(event->x, event->y);
1167 if (event->button == 3 && gtk_widget_get_visible(bookmark_hbox_)) {
1168 const BookmarkNode* node = GetNodeForToolButton(sender);
1170 DCHECK(page_navigator_);
1171 PopupMenuForNode(sender, node, event);
1177 void BookmarkBarGtk::OnClicked(GtkWidget* sender) {
1178 const BookmarkNode* node = GetNodeForToolButton(sender);
1180 DCHECK(node->is_url());
1181 DCHECK(page_navigator_);
1183 RecordAppLaunch(browser_->profile(), node->url());
1184 chrome::OpenAll(window_->GetNativeWindow(), page_navigator_, node,
1185 event_utils::DispositionForCurrentButtonPressEvent(),
1186 browser_->profile());
1188 RecordBookmarkLaunch(node, GetBookmarkLaunchLocation());
1191 void BookmarkBarGtk::OnButtonDragBegin(GtkWidget* button,
1192 GdkDragContext* drag_context) {
1193 GtkWidget* button_parent = gtk_widget_get_parent(button);
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);
1199 const BookmarkNode* node = GetNodeForToolButton(button);
1200 DCHECK(!dragged_node_);
1201 dragged_node_ = node;
1202 DCHECK(dragged_node_);
1204 drag_icon_ = GetDragRepresentationForNode(node, model_, theme_service_);
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.
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());
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);
1230 void BookmarkBarGtk::OnButtonDragEnd(GtkWidget* button,
1231 GdkDragContext* drag_context) {
1232 gtk_widget_show(button);
1233 gtk_widget_set_no_show_all(button, FALSE);
1235 ClearToolbarDropHighlighting();
1237 DCHECK(dragged_node_);
1238 dragged_node_ = NULL;
1241 gtk_widget_destroy(drag_icon_);
1244 g_object_unref(gtk_widget_get_parent(button));
1247 void BookmarkBarGtk::OnButtonDragGet(GtkWidget* widget,
1248 GdkDragContext* context,
1249 GtkSelectionData* selection_data,
1252 const BookmarkNode* node = BookmarkNodeForWidget(widget);
1253 WriteBookmarkToSelection(
1254 node, selection_data, target_type, browser_->profile());
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,
1264 browser_->OpenURL(params);
1265 RecordBookmarkAppsPageOpen(GetBookmarkLaunchLocation());
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);
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());
1285 gdk_event_free(event);
1288 gboolean BookmarkBarGtk::OnToolbarDragMotion(GtkWidget* toolbar,
1289 GdkDragContext* context,
1293 gint index = gtk_toolbar_get_drop_index(GTK_TOOLBAR(toolbar), x, y);
1294 return ItemDraggedOverToolbar(context, index, time);
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.
1306 last_allocation_width_ = allocation->width;
1311 void BookmarkBarGtk::OnDragReceived(GtkWidget* widget,
1312 GdkDragContext* context,
1314 GtkSelectionData* selection_data,
1315 guint target_type, guint time) {
1316 if (!edit_bookmarks_enabled_.GetValue()) {
1317 gtk_drag_finish(context, FALSE, FALSE, time);
1321 gboolean dnd_success = FALSE;
1322 gboolean delete_selection_data = FALSE;
1324 const BookmarkNode* dest_node = model_->bookmark_bar_node();
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();
1333 index = GetToolbarIndexForDragOverFolder(widget, x);
1335 dest_node = GetNodeForToolButton(widget);
1336 index = dest_node->child_count();
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;
1353 case ui::CHROME_NAMED_URL: {
1354 dnd_success = CreateNewBookmarkFromNamedUrl(
1355 selection_data, model_, dest_node, index);
1359 case ui::TEXT_URI_LIST: {
1360 dnd_success = CreateNewBookmarksFromURIList(
1361 selection_data, model_, dest_node, index);
1365 case ui::NETSCAPE_URL: {
1366 dnd_success = CreateNewBookmarkFromNetscapeURL(
1367 selection_data, model_, dest_node, index);
1371 case ui::TEXT_PLAIN: {
1372 guchar* text = gtk_selection_data_get_text(selection_data);
1375 GURL url(reinterpret_cast<char*>(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())
1381 base::string16 title = GetNameForURL(url);
1382 model_->AddURL(dest_node, index, title, url);
1388 gtk_drag_finish(context, dnd_success, delete_selection_data, time);
1391 void BookmarkBarGtk::OnDragLeave(GtkWidget* sender,
1392 GdkDragContext* context,
1394 if (GTK_IS_BUTTON(sender))
1395 gtk_drag_unhighlight(sender);
1397 ClearToolbarDropHighlighting();
1400 gboolean BookmarkBarGtk::OnFolderDragMotion(GtkWidget* button,
1401 GdkDragContext* context,
1405 if (!edit_bookmarks_enabled_.GetValue())
1407 GdkAtom target_type = gtk_drag_dest_find_target(button, context, NULL);
1408 if (target_type == GDK_NONE)
1411 int index = GetToolbarIndexForDragOverFolder(button, x);
1413 ClearToolbarDropHighlighting();
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);
1420 gdk_drag_status(context, GDK_ACTION_COPY, time);
1426 // Remove previous highlighting.
1427 gtk_drag_unhighlight(button);
1428 return ItemDraggedOverToolbar(context, index, time);
1431 gboolean BookmarkBarGtk::OnEventBoxExpose(GtkWidget* widget,
1432 GdkEventExpose* event) {
1433 TRACE_EVENT0("ui::gtk", "BookmarkBarGtk::OnEventBoxExpose");
1434 GtkThemeService* theme_provider = theme_service_;
1436 // We don't need to render the toolbar image in GTK mode, except when
1438 if (theme_provider->UsingNativeTheme() &&
1439 bookmark_bar_state_ != BookmarkBar::DETACHED)
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);
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,
1455 gfx::Size web_contents_size;
1456 if (!GetWebContentsSize(&web_contents_size))
1458 gfx::CanvasSkiaPaint canvas(event, true);
1460 GtkAllocation allocation;
1461 gtk_widget_get_allocation(widget, &allocation);
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());
1470 return FALSE; // Propagate expose to children.
1473 void BookmarkBarGtk::OnEventBoxDestroy(GtkWidget* widget) {
1475 model_->RemoveObserver(this);
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
1485 if (bookmark_bar_state_ == BookmarkBar::DETACHED) {
1486 base::MessageLoop::current()->PostTask(
1488 base::Bind(&BookmarkBarGtk::PaintEventBox, weak_factory_.GetWeakPtr()));
1492 void BookmarkBarGtk::OnThrobbingWidgetDestroy(GtkWidget* widget) {
1493 SetThrobbingWidget(NULL);
1496 void BookmarkBarGtk::ShowImportDialog() {
1497 chrome::ShowImportDialog(browser_);
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);
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);