// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.h" #include #include "base/bind.h" #include "base/debug/trace_event.h" #include "base/metrics/histogram.h" #include "base/pickle.h" #include "base/prefs/pref_service.h" #include "base/utf_string_conversions.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/bookmarks/bookmark_model_factory.h" #include "chrome/browser/bookmarks/bookmark_node_data.h" #include "chrome/browser/bookmarks/bookmark_utils.h" #include "chrome/browser/browser_shutdown.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/themes/theme_properties.h" #include "chrome/browser/ui/bookmarks/bookmark_bar_constants.h" #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h" #include "chrome/browser/ui/bookmarks/bookmark_utils.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/chrome_pages.h" #include "chrome/browser/ui/gtk/bookmarks/bookmark_bar_instructions_gtk.h" #include "chrome/browser/ui/gtk/bookmarks/bookmark_menu_controller_gtk.h" #include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h" #include "chrome/browser/ui/gtk/browser_window_gtk.h" #include "chrome/browser/ui/gtk/custom_button.h" #include "chrome/browser/ui/gtk/event_utils.h" #include "chrome/browser/ui/gtk/gtk_chrome_button.h" #include "chrome/browser/ui/gtk/gtk_theme_service.h" #include "chrome/browser/ui/gtk/gtk_util.h" #include "chrome/browser/ui/gtk/hover_controller_gtk.h" #include "chrome/browser/ui/gtk/menu_gtk.h" #include "chrome/browser/ui/gtk/rounded_window.h" #include "chrome/browser/ui/gtk/tabstrip_origin_provider.h" #include "chrome/browser/ui/gtk/view_id_util.h" #include "chrome/browser/ui/ntp_background_util.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/webui/ntp/app_launcher_handler.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/pref_names.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/user_metrics.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "grit/ui_resources.h" #include "ui/base/dragdrop/drag_drop_types.h" #include "ui/base/dragdrop/gtk_dnd_util.h" #include "ui/base/gtk/gtk_compat.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/canvas_skia_paint.h" #include "ui/gfx/gtk_util.h" #include "ui/gfx/image/cairo_cached_surface.h" #include "ui/gfx/image/image.h" using content::PageNavigator; using content::UserMetricsAction; using content::WebContents; namespace { // The showing height of the bar. const int kBookmarkBarHeight = 29; // Padding for when the bookmark bar is detached. const int kTopBottomNTPPadding = 12; const int kLeftRightNTPPadding = 8; // Padding around the bar's content area when the bookmark bar is detached. const int kNTPPadding = 2; // The number of pixels of rounding on the corners of the bookmark bar content // area when in detached mode. const int kNTPRoundedness = 3; // The height of the bar when it is "hidden". It is usually not completely // hidden because even when it is closed it forms the bottom few pixels of // the toolbar. const int kBookmarkBarMinimumHeight = 3; // Left-padding for the instructional text. const int kInstructionsPadding = 6; // Padding around the "Other Bookmarks" button. const int kOtherBookmarksPaddingHorizontal = 2; const int kOtherBookmarksPaddingVertical = 1; // The targets accepted by the toolbar and folder buttons for DnD. const int kDestTargetList[] = { ui::CHROME_BOOKMARK_ITEM, ui::CHROME_NAMED_URL, ui::TEXT_URI_LIST, ui::NETSCAPE_URL, ui::TEXT_PLAIN, -1 }; // Acceptable drag actions for the bookmark bar drag destinations. const GdkDragAction kDragAction = GdkDragAction(GDK_ACTION_MOVE | GDK_ACTION_COPY); void SetToolBarStyle() { static bool style_was_set = false; if (style_was_set) return; style_was_set = true; gtk_rc_parse_string( "style \"chrome-bookmark-toolbar\" {" " xthickness = 0\n" " ythickness = 0\n" " GtkWidget::focus-padding = 0\n" " GtkContainer::border-width = 0\n" " GtkToolbar::internal-padding = 1\n" " GtkToolbar::shadow-type = GTK_SHADOW_NONE\n" "}\n" "widget \"*chrome-bookmark-toolbar\" style \"chrome-bookmark-toolbar\""); } void RecordAppLaunch(Profile* profile, const GURL& url) { DCHECK(profile->GetExtensionService()); const extensions::Extension* extension = profile->GetExtensionService()->GetInstalledApp(url); if (!extension) return; AppLauncherHandler::RecordAppLaunchType( extension_misc::APP_LAUNCH_BOOKMARK_BAR, extension->GetType()); } } // namespace BookmarkBarGtk::BookmarkBarGtk(BrowserWindowGtk* window, Browser* browser, TabstripOriginProvider* tabstrip_origin_provider) : page_navigator_(NULL), browser_(browser), window_(window), tabstrip_origin_provider_(tabstrip_origin_provider), model_(NULL), instructions_(NULL), dragged_node_(NULL), drag_icon_(NULL), toolbar_drop_item_(NULL), theme_service_(GtkThemeService::GetFrom(browser->profile())), show_instructions_(true), menu_bar_helper_(this), slide_animation_(this), last_allocation_width_(-1), throbbing_widget_(NULL), weak_factory_(this), bookmark_bar_state_(BookmarkBar::DETACHED), max_height_(0) { Init(); // Force an update by simulating being in the wrong state. // BrowserWindowGtk sets our true state after we're created. SetBookmarkBarState(BookmarkBar::SHOW, BookmarkBar::DONT_ANIMATE_STATE_CHANGE); registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED, content::Source(theme_service_)); edit_bookmarks_enabled_.Init( prefs::kEditBookmarksEnabled, browser_->profile()->GetPrefs(), base::Bind(&BookmarkBarGtk::OnEditBookmarksEnabledChanged, base::Unretained(this))); OnEditBookmarksEnabledChanged(); } BookmarkBarGtk::~BookmarkBarGtk() { RemoveAllButtons(); bookmark_toolbar_.Destroy(); event_box_.Destroy(); } void BookmarkBarGtk::SetPageNavigator(PageNavigator* navigator) { page_navigator_ = navigator; } void BookmarkBarGtk::Init() { event_box_.Own(gtk_event_box_new()); g_signal_connect(event_box_.get(), "destroy", G_CALLBACK(&OnEventBoxDestroyThunk), this); g_signal_connect(event_box_.get(), "button-press-event", G_CALLBACK(&OnButtonPressedThunk), this); ntp_padding_box_ = gtk_alignment_new(0, 0, 1, 1); gtk_container_add(GTK_CONTAINER(event_box_.get()), ntp_padding_box_); paint_box_ = gtk_event_box_new(); gtk_container_add(GTK_CONTAINER(ntp_padding_box_), paint_box_); GdkColor paint_box_color = theme_service_->GetGdkColor(ThemeProperties::COLOR_TOOLBAR); gtk_widget_modify_bg(paint_box_, GTK_STATE_NORMAL, &paint_box_color); gtk_widget_add_events(paint_box_, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK); bookmark_hbox_ = gtk_hbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(paint_box_), bookmark_hbox_); instructions_ = gtk_alignment_new(0, 0, 1, 1); gtk_alignment_set_padding(GTK_ALIGNMENT(instructions_), 0, 0, kInstructionsPadding, 0); Profile* profile = browser_->profile(); instructions_gtk_.reset(new BookmarkBarInstructionsGtk(this, profile)); gtk_container_add(GTK_CONTAINER(instructions_), instructions_gtk_->widget()); gtk_box_pack_start(GTK_BOX(bookmark_hbox_), instructions_, TRUE, TRUE, 0); gtk_drag_dest_set(instructions_, GtkDestDefaults(GTK_DEST_DEFAULT_DROP | GTK_DEST_DEFAULT_MOTION), NULL, 0, kDragAction); ui::SetDestTargetList(instructions_, kDestTargetList); g_signal_connect(instructions_, "drag-data-received", G_CALLBACK(&OnDragReceivedThunk), this); g_signal_connect(event_box_.get(), "expose-event", G_CALLBACK(&OnEventBoxExposeThunk), this); UpdateEventBoxPaintability(); bookmark_toolbar_.Own(gtk_toolbar_new()); SetToolBarStyle(); gtk_widget_set_name(bookmark_toolbar_.get(), "chrome-bookmark-toolbar"); gtk_util::SuppressDefaultPainting(bookmark_toolbar_.get()); g_signal_connect(bookmark_toolbar_.get(), "size-allocate", G_CALLBACK(&OnToolbarSizeAllocateThunk), this); gtk_box_pack_start(GTK_BOX(bookmark_hbox_), bookmark_toolbar_.get(), TRUE, TRUE, 0); overflow_button_ = theme_service_->BuildChromeButton(); g_object_set_data(G_OBJECT(overflow_button_), "left-align-popup", reinterpret_cast(true)); SetOverflowButtonAppearance(); ConnectFolderButtonEvents(overflow_button_, false); gtk_box_pack_start(GTK_BOX(bookmark_hbox_), overflow_button_, FALSE, FALSE, 0); gtk_drag_dest_set(bookmark_toolbar_.get(), GTK_DEST_DEFAULT_DROP, NULL, 0, kDragAction); ui::SetDestTargetList(bookmark_toolbar_.get(), kDestTargetList); g_signal_connect(bookmark_toolbar_.get(), "drag-motion", G_CALLBACK(&OnToolbarDragMotionThunk), this); g_signal_connect(bookmark_toolbar_.get(), "drag-leave", G_CALLBACK(&OnDragLeaveThunk), this); g_signal_connect(bookmark_toolbar_.get(), "drag-data-received", G_CALLBACK(&OnDragReceivedThunk), this); other_bookmarks_separator_ = theme_service_->CreateToolbarSeparator(); gtk_box_pack_start(GTK_BOX(bookmark_hbox_), other_bookmarks_separator_, FALSE, FALSE, 0); // We pack the button manually (rather than using gtk_button_set_*) so that // we can have finer control over its label. other_bookmarks_button_ = theme_service_->BuildChromeButton(); gtk_widget_show_all(other_bookmarks_button_); ConnectFolderButtonEvents(other_bookmarks_button_, false); other_padding_ = gtk_alignment_new(0, 0, 1, 1); gtk_alignment_set_padding(GTK_ALIGNMENT(other_padding_), kOtherBookmarksPaddingVertical, kOtherBookmarksPaddingVertical, kOtherBookmarksPaddingHorizontal, kOtherBookmarksPaddingHorizontal); gtk_container_add(GTK_CONTAINER(other_padding_), other_bookmarks_button_); gtk_box_pack_start(GTK_BOX(bookmark_hbox_), other_padding_, FALSE, FALSE, 0); gtk_widget_set_no_show_all(other_padding_, TRUE); gtk_widget_set_size_request(event_box_.get(), -1, kBookmarkBarMinimumHeight); ViewIDUtil::SetID(other_bookmarks_button_, VIEW_ID_OTHER_BOOKMARKS); ViewIDUtil::SetID(widget(), VIEW_ID_BOOKMARK_BAR); gtk_widget_show_all(widget()); gtk_widget_hide(widget()); AddCoreButtons(); // TODO(erg): Handle extensions model_ = BookmarkModelFactory::GetForProfile(profile); model_->AddObserver(this); if (model_->IsLoaded()) Loaded(model_, false); // else case: we'll receive notification back from the BookmarkModel when done // loading, then we'll populate the bar. } void BookmarkBarGtk::SetBookmarkBarState( BookmarkBar::State state, BookmarkBar::AnimateChangeType animate_type) { TRACE_EVENT0("ui::gtk", "BookmarkBarGtk::SetBookmarkBarState"); if (animate_type == BookmarkBar::ANIMATE_STATE_CHANGE && (state == BookmarkBar::DETACHED || bookmark_bar_state_ == BookmarkBar::DETACHED)) { // TODO(estade): animate the transition between detached and non or remove // detached entirely. animate_type = BookmarkBar::DONT_ANIMATE_STATE_CHANGE; } BookmarkBar::State old_state = bookmark_bar_state_; bookmark_bar_state_ = state; if (state == BookmarkBar::SHOW || state == BookmarkBar::DETACHED) Show(old_state, animate_type); else Hide(old_state, animate_type); } int BookmarkBarGtk::GetHeight() { GtkAllocation allocation; gtk_widget_get_allocation(event_box_.get(), &allocation); return allocation.height - kBookmarkBarMinimumHeight; } bool BookmarkBarGtk::IsAnimating() { return slide_animation_.is_animating(); } void BookmarkBarGtk::CalculateMaxHeight() { if (theme_service_->UsingNativeTheme()) { // Get the requisition of our single child instead of the event box itself // because the event box probably already has a size request. GtkRequisition req; gtk_widget_size_request(ntp_padding_box_, &req); max_height_ = req.height; } else { max_height_ = (bookmark_bar_state_ == BookmarkBar::DETACHED) ? chrome::kNTPBookmarkBarHeight : kBookmarkBarHeight; } } void BookmarkBarGtk::AnimationProgressed(const ui::Animation* animation) { DCHECK_EQ(animation, &slide_animation_); gint height = static_cast(animation->GetCurrentValue() * (max_height_ - kBookmarkBarMinimumHeight)) + kBookmarkBarMinimumHeight; gtk_widget_set_size_request(event_box_.get(), -1, height); } void BookmarkBarGtk::AnimationEnded(const ui::Animation* animation) { DCHECK_EQ(animation, &slide_animation_); if (!slide_animation_.IsShowing()) { gtk_widget_hide(bookmark_hbox_); // We can be windowless during unit tests. if (window_) { // Because of our constant resizing and our toolbar/bookmark bar overlap // shenanigans, gtk+ gets confused, partially draws parts of the bookmark // bar into the toolbar and than doesn't queue a redraw to fix it. So do // it manually by telling the toolbar area to redraw itself. window_->QueueToolbarRedraw(); } } } // MenuBarHelper::Delegate implementation -------------------------------------- void BookmarkBarGtk::PopupForButton(GtkWidget* button) { const BookmarkNode* node = GetNodeForToolButton(button); DCHECK(node); DCHECK(page_navigator_); int first_hidden = GetFirstHiddenBookmark(0, NULL); if (first_hidden == -1) { // No overflow exists: don't show anything for the overflow button. if (button == overflow_button_) return; } else { // Overflow exists: don't show anything for an overflowed folder button. if (button != overflow_button_ && button != other_bookmarks_button_ && node->parent()->GetIndexOf(node) >= first_hidden) { return; } } current_menu_.reset( new BookmarkMenuController(browser_, page_navigator_, GTK_WINDOW(gtk_widget_get_toplevel(button)), node, button == overflow_button_ ? first_hidden : 0)); menu_bar_helper_.MenuStartedShowing(button, current_menu_->widget()); GdkEvent* event = gtk_get_current_event(); current_menu_->Popup(button, event->button.button, event->button.time); gdk_event_free(event); } void BookmarkBarGtk::PopupForButtonNextTo(GtkWidget* button, GtkMenuDirectionType dir) { const BookmarkNode* relative_node = GetNodeForToolButton(button); DCHECK(relative_node); // Find out the order of the buttons. std::vector folder_list; const int first_hidden = GetFirstHiddenBookmark(0, &folder_list); if (first_hidden != -1) folder_list.push_back(overflow_button_); folder_list.push_back(other_bookmarks_button_); // Find the position of |button|. int button_idx = -1; for (size_t i = 0; i < folder_list.size(); ++i) { if (folder_list[i] == button) { button_idx = i; break; } } DCHECK_NE(button_idx, -1); // Find the GtkWidget* for the actual target button. int shift = dir == GTK_MENU_DIR_PARENT ? -1 : 1; button_idx = (button_idx + shift + folder_list.size()) % folder_list.size(); PopupForButton(folder_list[button_idx]); } void BookmarkBarGtk::CloseMenu() { current_context_menu_->Cancel(); } void BookmarkBarGtk::Show(BookmarkBar::State old_state, BookmarkBar::AnimateChangeType animate_type) { gtk_widget_show_all(widget()); UpdateDetachedState(old_state); CalculateMaxHeight(); if (animate_type == BookmarkBar::ANIMATE_STATE_CHANGE) { slide_animation_.Show(); } else { slide_animation_.Reset(1); AnimationProgressed(&slide_animation_); } if (model_ && model_->IsLoaded()) UpdateOtherBookmarksVisibility(); // Hide out behind the findbar. This is rather fragile code, it could // probably be improved. if (bookmark_bar_state_ == BookmarkBar::DETACHED) { if (theme_service_->UsingNativeTheme()) { GtkWidget* parent = gtk_widget_get_parent(event_box_.get()); if (gtk_widget_get_realized(parent)) gdk_window_lower(gtk_widget_get_window(parent)); if (gtk_widget_get_realized(event_box_.get())) gdk_window_lower(gtk_widget_get_window(event_box_.get())); } else { // Chromium theme mode. if (gtk_widget_get_realized(paint_box_)) { gdk_window_lower(gtk_widget_get_window(paint_box_)); // The event box won't stay below its children's GdkWindows unless we // toggle the above-child property here. If the event box doesn't stay // below its children then events will be routed to it rather than the // children. gtk_event_box_set_above_child(GTK_EVENT_BOX(event_box_.get()), TRUE); gtk_event_box_set_above_child(GTK_EVENT_BOX(event_box_.get()), FALSE); } } } // Maybe show the instructions gtk_widget_set_visible(bookmark_toolbar_.get(), !show_instructions_); gtk_widget_set_visible(instructions_, show_instructions_); SetChevronState(); } void BookmarkBarGtk::Hide(BookmarkBar::State old_state, BookmarkBar::AnimateChangeType animate_type) { UpdateDetachedState(old_state); // After coming out of fullscreen, the browser window sets the bookmark bar // to the "hidden" state, which means we need to show our minimum height. if (!window_->IsFullscreen()) gtk_widget_show(widget()); CalculateMaxHeight(); // Sometimes we get called without a matching call to open. If that happens // then force hide. if (slide_animation_.IsShowing() && animate_type == BookmarkBar::ANIMATE_STATE_CHANGE) { slide_animation_.Hide(); } else { gtk_widget_hide(bookmark_hbox_); slide_animation_.Reset(0); AnimationProgressed(&slide_animation_); } } void BookmarkBarGtk::SetInstructionState() { if (model_) show_instructions_ = model_->bookmark_bar_node()->empty(); gtk_widget_set_visible(bookmark_toolbar_.get(), !show_instructions_); gtk_widget_set_visible(instructions_, show_instructions_); } void BookmarkBarGtk::SetChevronState() { if (!gtk_widget_get_visible(bookmark_hbox_)) return; if (show_instructions_) { gtk_widget_hide(overflow_button_); return; } int extra_space = 0; if (gtk_widget_get_visible(overflow_button_)) { GtkAllocation allocation; gtk_widget_get_allocation(overflow_button_, &allocation); extra_space = allocation.width; } int overflow_idx = GetFirstHiddenBookmark(extra_space, NULL); if (overflow_idx == -1) gtk_widget_hide(overflow_button_); else gtk_widget_show_all(overflow_button_); } void BookmarkBarGtk::UpdateOtherBookmarksVisibility() { bool has_other_children = !model_->other_node()->empty(); gtk_widget_set_visible(other_padding_, has_other_children); gtk_widget_set_visible(other_bookmarks_separator_, has_other_children); } void BookmarkBarGtk::RemoveAllButtons() { gtk_util::RemoveAllChildren(bookmark_toolbar_.get()); menu_bar_helper_.Clear(); } void BookmarkBarGtk::AddCoreButtons() { menu_bar_helper_.Add(other_bookmarks_button_); menu_bar_helper_.Add(overflow_button_); } void BookmarkBarGtk::ResetButtons() { RemoveAllButtons(); AddCoreButtons(); const BookmarkNode* bar = model_->bookmark_bar_node(); DCHECK(bar && model_->other_node()); // Create a button for each of the children on the bookmark bar. for (int i = 0; i < bar->child_count(); ++i) { const BookmarkNode* node = bar->GetChild(i); GtkToolItem* item = CreateBookmarkToolItem(node); gtk_toolbar_insert(GTK_TOOLBAR(bookmark_toolbar_.get()), item, -1); if (node->is_folder()) menu_bar_helper_.Add(gtk_bin_get_child(GTK_BIN(item))); } bookmark_utils::ConfigureButtonForNode(model_->other_node(), model_, other_bookmarks_button_, theme_service_); SetInstructionState(); SetChevronState(); } int BookmarkBarGtk::GetBookmarkButtonCount() { GList* children = gtk_container_get_children( GTK_CONTAINER(bookmark_toolbar_.get())); int count = g_list_length(children); g_list_free(children); return count; } bookmark_utils::BookmarkLaunchLocation BookmarkBarGtk::GetBookmarkLaunchLocation() const { return bookmark_bar_state_ == BookmarkBar::DETACHED ? bookmark_utils::LAUNCH_DETACHED_BAR : bookmark_utils::LAUNCH_ATTACHED_BAR; } void BookmarkBarGtk::SetOverflowButtonAppearance() { GtkWidget* former_child = gtk_bin_get_child(GTK_BIN(overflow_button_)); if (former_child) gtk_widget_destroy(former_child); GtkWidget* new_child; if (theme_service_->UsingNativeTheme()) { new_child = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE); } else { const gfx::Image& image = ui::ResourceBundle::GetSharedInstance(). GetNativeImageNamed(IDR_BOOKMARK_BAR_CHEVRONS, ui::ResourceBundle::RTL_ENABLED); new_child = gtk_image_new_from_pixbuf(image.ToGdkPixbuf()); } gtk_container_add(GTK_CONTAINER(overflow_button_), new_child); SetChevronState(); } int BookmarkBarGtk::GetFirstHiddenBookmark(int extra_space, std::vector* showing_folders) { int rv = 0; // We're going to keep track of how much width we've used as we move along // the bookmark bar. If we ever surpass the width of the bookmark bar, we'll // know that's the first hidden bookmark. int width_used = 0; // GTK appears to require one pixel of padding to the side of the first and // last buttons on the bar. // TODO(gideonwald): figure out the precise source of these extra two pixels // and make this calculation more reliable. GtkAllocation allocation; gtk_widget_get_allocation(bookmark_toolbar_.get(), &allocation); int total_width = allocation.width - 2; bool overflow = false; GtkRequisition requested_size_; GList* toolbar_items = gtk_container_get_children(GTK_CONTAINER(bookmark_toolbar_.get())); for (GList* iter = toolbar_items; iter; iter = g_list_next(iter)) { GtkWidget* tool_item = reinterpret_cast(iter->data); gtk_widget_size_request(tool_item, &requested_size_); width_used += requested_size_.width; // |extra_space| is available if we can remove the chevron, which happens // only if there are no more potential overflow bookmarks after this one. overflow = width_used > total_width + (g_list_next(iter) ? 0 : extra_space); if (overflow) break; if (showing_folders && model_->bookmark_bar_node()->GetChild(rv)->is_folder()) { showing_folders->push_back(gtk_bin_get_child(GTK_BIN(tool_item))); } rv++; } g_list_free(toolbar_items); if (!overflow) return -1; return rv; } void BookmarkBarGtk::UpdateDetachedState(BookmarkBar::State old_state) { bool old_detached = old_state == BookmarkBar::DETACHED; bool detached = bookmark_bar_state_ == BookmarkBar::DETACHED; if (detached == old_detached) return; if (detached) { gtk_event_box_set_visible_window(GTK_EVENT_BOX(paint_box_), TRUE); GdkColor stroke_color = theme_service_->UsingNativeTheme() ? theme_service_->GetBorderColor() : theme_service_->GetGdkColor(ThemeProperties::COLOR_NTP_HEADER); gtk_util::ActAsRoundedWindow(paint_box_, stroke_color, kNTPRoundedness, gtk_util::ROUNDED_ALL, gtk_util::BORDER_ALL); gtk_alignment_set_padding(GTK_ALIGNMENT(ntp_padding_box_), kTopBottomNTPPadding, kTopBottomNTPPadding, kLeftRightNTPPadding, kLeftRightNTPPadding); gtk_container_set_border_width(GTK_CONTAINER(bookmark_hbox_), kNTPPadding); } else { gtk_util::StopActingAsRoundedWindow(paint_box_); gtk_event_box_set_visible_window(GTK_EVENT_BOX(paint_box_), FALSE); gtk_alignment_set_padding(GTK_ALIGNMENT(ntp_padding_box_), 0, 0, 0, 0); gtk_container_set_border_width(GTK_CONTAINER(bookmark_hbox_), 0); } UpdateEventBoxPaintability(); // |window_| can be NULL during testing. // Listen for parent size allocations. Only connect once. if (window_ && detached) { GtkWidget* parent = gtk_widget_get_parent(widget()); if (parent && g_signal_handler_find(parent, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, reinterpret_cast(OnParentSizeAllocateThunk), NULL) == 0) { g_signal_connect(parent, "size-allocate", G_CALLBACK(OnParentSizeAllocateThunk), this); } } } void BookmarkBarGtk::UpdateEventBoxPaintability() { gtk_widget_set_app_paintable( event_box_.get(), (!theme_service_->UsingNativeTheme() || bookmark_bar_state_ == BookmarkBar::DETACHED)); // When using the GTK+ theme, we need to have the event box be visible so // buttons don't get a halo color from the background. When using Chromium // themes, we want to let the background show through the toolbar. gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_.get()), theme_service_->UsingNativeTheme()); } void BookmarkBarGtk::PaintEventBox() { gfx::Size web_contents_size; if (GetWebContentsSize(&web_contents_size) && web_contents_size != last_web_contents_size_) { last_web_contents_size_ = web_contents_size; gtk_widget_queue_draw(event_box_.get()); } } bool BookmarkBarGtk::GetWebContentsSize(gfx::Size* size) { Browser* browser = browser_; if (!browser) { NOTREACHED(); return false; } WebContents* web_contents = browser->tab_strip_model()->GetActiveWebContents(); if (!web_contents) { // It is possible to have a browser but no WebContents while under testing, // so don't NOTREACHED() and error the program. return false; } if (!web_contents->GetView()) { NOTREACHED(); return false; } *size = web_contents->GetView()->GetContainerSize(); return true; } void BookmarkBarGtk::StartThrobbingAfterAllocation(GtkWidget* item) { g_signal_connect_after( item, "size-allocate", G_CALLBACK(OnItemAllocateThunk), this); } void BookmarkBarGtk::OnItemAllocate(GtkWidget* item, GtkAllocation* allocation) { // We only want to fire on the item's first allocation. g_signal_handlers_disconnect_by_func( item, reinterpret_cast(&OnItemAllocateThunk), this); GtkWidget* button = gtk_bin_get_child(GTK_BIN(item)); const BookmarkNode* node = GetNodeForToolButton(button); if (node) StartThrobbing(node); } void BookmarkBarGtk::StartThrobbing(const BookmarkNode* node) { const BookmarkNode* parent_on_bb = NULL; for (const BookmarkNode* parent = node; parent; parent = parent->parent()) { if (parent->parent() == model_->bookmark_bar_node()) { parent_on_bb = parent; break; } } GtkWidget* widget_to_throb = NULL; if (!parent_on_bb) { // Descendant of "Other Bookmarks". widget_to_throb = other_bookmarks_button_; } else { int hidden = GetFirstHiddenBookmark(0, NULL); int idx = model_->bookmark_bar_node()->GetIndexOf(parent_on_bb); if (hidden >= 0 && hidden <= idx) { widget_to_throb = overflow_button_; } else { widget_to_throb = gtk_bin_get_child(GTK_BIN(gtk_toolbar_get_nth_item( GTK_TOOLBAR(bookmark_toolbar_.get()), idx))); } } SetThrobbingWidget(widget_to_throb); } void BookmarkBarGtk::SetThrobbingWidget(GtkWidget* widget) { if (throbbing_widget_) { HoverControllerGtk* hover_controller = HoverControllerGtk::GetHoverControllerGtk(throbbing_widget_); if (hover_controller) hover_controller->StartThrobbing(0); g_signal_handlers_disconnect_by_func( throbbing_widget_, reinterpret_cast(OnThrobbingWidgetDestroyThunk), this); g_object_unref(throbbing_widget_); throbbing_widget_ = NULL; } if (widget) { throbbing_widget_ = widget; g_object_ref(throbbing_widget_); g_signal_connect(throbbing_widget_, "destroy", G_CALLBACK(OnThrobbingWidgetDestroyThunk), this); HoverControllerGtk* hover_controller = HoverControllerGtk::GetHoverControllerGtk(throbbing_widget_); if (hover_controller) hover_controller->StartThrobbing(4); } } gboolean BookmarkBarGtk::ItemDraggedOverToolbar(GdkDragContext* context, int index, guint time) { if (!edit_bookmarks_enabled_.GetValue()) return FALSE; GdkAtom target_type = gtk_drag_dest_find_target(bookmark_toolbar_.get(), context, NULL); if (target_type == GDK_NONE) { // We shouldn't act like a drop target when something that we can't deal // with is dragged over the toolbar. return FALSE; } if (!toolbar_drop_item_) { if (dragged_node_) { toolbar_drop_item_ = CreateBookmarkToolItem(dragged_node_); g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_)); } else { // Create a fake item the size of other_node(). // // TODO(erg): Maybe somehow figure out the real size for the drop target? toolbar_drop_item_ = CreateBookmarkToolItem(model_->other_node()); g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_)); } } gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_.get()), GTK_TOOL_ITEM(toolbar_drop_item_), index); if (target_type == ui::GetAtomForTarget(ui::CHROME_BOOKMARK_ITEM)) { gdk_drag_status(context, GDK_ACTION_MOVE, time); } else { gdk_drag_status(context, GDK_ACTION_COPY, time); } return TRUE; } int BookmarkBarGtk::GetToolbarIndexForDragOverFolder(GtkWidget* button, gint x) { GtkAllocation allocation; gtk_widget_get_allocation(button, &allocation); int margin = std::min(15, static_cast(0.3 * allocation.width)); if (x > margin && x < (allocation.width - margin)) return -1; GtkWidget* parent = gtk_widget_get_parent(button); gint index = gtk_toolbar_get_item_index(GTK_TOOLBAR(bookmark_toolbar_.get()), GTK_TOOL_ITEM(parent)); if (x > margin) index++; return index; } void BookmarkBarGtk::ClearToolbarDropHighlighting() { if (toolbar_drop_item_) { g_object_unref(toolbar_drop_item_); toolbar_drop_item_ = NULL; } gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_.get()), NULL, 0); } void BookmarkBarGtk::Loaded(BookmarkModel* model, bool ids_reassigned) { // If |instructions_| has been nulled, we are in the middle of browser // shutdown. Do nothing. if (!instructions_) return; UpdateOtherBookmarksVisibility(); ResetButtons(); } void BookmarkBarGtk::BookmarkModelBeingDeleted(BookmarkModel* model) { // The bookmark model should never be deleted before us. This code exists // to check for regressions in shutdown code and not crash. if (!browser_shutdown::ShuttingDownWithoutClosingBrowsers()) NOTREACHED(); // Do minimal cleanup, presumably we'll be deleted shortly. model_->RemoveObserver(this); model_ = NULL; } void BookmarkBarGtk::BookmarkNodeMoved(BookmarkModel* model, const BookmarkNode* old_parent, int old_index, const BookmarkNode* new_parent, int new_index) { const BookmarkNode* node = new_parent->GetChild(new_index); BookmarkNodeRemoved(model, old_parent, old_index, node); BookmarkNodeAdded(model, new_parent, new_index); } void BookmarkBarGtk::BookmarkNodeAdded(BookmarkModel* model, const BookmarkNode* parent, int index) { UpdateOtherBookmarksVisibility(); const BookmarkNode* node = parent->GetChild(index); if (parent != model_->bookmark_bar_node()) { StartThrobbing(node); return; } DCHECK(index >= 0 && index <= GetBookmarkButtonCount()); GtkToolItem* item = CreateBookmarkToolItem(node); gtk_toolbar_insert(GTK_TOOLBAR(bookmark_toolbar_.get()), item, index); if (node->is_folder()) menu_bar_helper_.Add(gtk_bin_get_child(GTK_BIN(item))); SetInstructionState(); SetChevronState(); StartThrobbingAfterAllocation(GTK_WIDGET(item)); } void BookmarkBarGtk::BookmarkNodeRemoved(BookmarkModel* model, const BookmarkNode* parent, int old_index, const BookmarkNode* node) { UpdateOtherBookmarksVisibility(); if (parent != model_->bookmark_bar_node()) { // We only care about nodes on the bookmark bar. return; } DCHECK(old_index >= 0 && old_index < GetBookmarkButtonCount()); GtkWidget* to_remove = GTK_WIDGET(gtk_toolbar_get_nth_item( GTK_TOOLBAR(bookmark_toolbar_.get()), old_index)); if (node->is_folder()) menu_bar_helper_.Remove(gtk_bin_get_child(GTK_BIN(to_remove))); gtk_container_remove(GTK_CONTAINER(bookmark_toolbar_.get()), to_remove); SetInstructionState(); SetChevronState(); } void BookmarkBarGtk::BookmarkAllNodesRemoved(BookmarkModel* model) { UpdateOtherBookmarksVisibility(); ResetButtons(); } void BookmarkBarGtk::BookmarkNodeChanged(BookmarkModel* model, const BookmarkNode* node) { if (node->parent() != model_->bookmark_bar_node()) { // We only care about nodes on the bookmark bar. return; } int index = model_->bookmark_bar_node()->GetIndexOf(node); DCHECK(index != -1); GtkToolItem* item = gtk_toolbar_get_nth_item( GTK_TOOLBAR(bookmark_toolbar_.get()), index); GtkWidget* button = gtk_bin_get_child(GTK_BIN(item)); bookmark_utils::ConfigureButtonForNode(node, model, button, theme_service_); SetChevronState(); } void BookmarkBarGtk::BookmarkNodeFaviconChanged(BookmarkModel* model, const BookmarkNode* node) { BookmarkNodeChanged(model, node); } void BookmarkBarGtk::BookmarkNodeChildrenReordered(BookmarkModel* model, const BookmarkNode* node) { if (node != model_->bookmark_bar_node()) return; // We only care about reordering of the bookmark bar node. ResetButtons(); } void BookmarkBarGtk::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) { if (model_ && model_->IsLoaded()) { // Regenerate the bookmark bar with all new objects with their theme // properties set correctly for the new theme. ResetButtons(); } // Resize the bookmark bar since the target height may have changed. CalculateMaxHeight(); AnimationProgressed(&slide_animation_); UpdateEventBoxPaintability(); GdkColor paint_box_color = theme_service_->GetGdkColor(ThemeProperties::COLOR_TOOLBAR); gtk_widget_modify_bg(paint_box_, GTK_STATE_NORMAL, &paint_box_color); if (bookmark_bar_state_ == BookmarkBar::DETACHED) { GdkColor stroke_color = theme_service_->UsingNativeTheme() ? theme_service_->GetBorderColor() : theme_service_->GetGdkColor(ThemeProperties::COLOR_NTP_HEADER); gtk_util::SetRoundedWindowBorderColor(paint_box_, stroke_color); } SetOverflowButtonAppearance(); } } GtkWidget* BookmarkBarGtk::CreateBookmarkButton(const BookmarkNode* node) { GtkWidget* button = theme_service_->BuildChromeButton(); bookmark_utils::ConfigureButtonForNode(node, model_, button, theme_service_); // The tool item is also a source for dragging gtk_drag_source_set(button, GDK_BUTTON1_MASK, NULL, 0, static_cast(GDK_ACTION_MOVE | GDK_ACTION_COPY)); int target_mask = bookmark_utils::GetCodeMask(node->is_folder()); ui::SetSourceTargetListFromCodeMask(button, target_mask); g_signal_connect(button, "drag-begin", G_CALLBACK(&OnButtonDragBeginThunk), this); g_signal_connect(button, "drag-end", G_CALLBACK(&OnButtonDragEndThunk), this); g_signal_connect(button, "drag-data-get", G_CALLBACK(&OnButtonDragGetThunk), this); // We deliberately don't connect to "drag-data-delete" because the action of // moving a button will regenerate all the contents of the bookmarks bar // anyway. if (node->is_url()) { // Connect to 'button-release-event' instead of 'clicked' because we need // access to the modifier keys and we do different things on each // button. g_signal_connect(button, "button-press-event", G_CALLBACK(OnButtonPressedThunk), this); g_signal_connect(button, "clicked", G_CALLBACK(OnClickedThunk), this); gtk_util::SetButtonTriggersNavigation(button); } else { ConnectFolderButtonEvents(button, true); } return button; } GtkToolItem* BookmarkBarGtk::CreateBookmarkToolItem(const BookmarkNode* node) { GtkWidget* button = CreateBookmarkButton(node); g_object_set_data(G_OBJECT(button), "left-align-popup", reinterpret_cast(true)); GtkToolItem* item = gtk_tool_item_new(); gtk_container_add(GTK_CONTAINER(item), button); gtk_widget_show_all(GTK_WIDGET(item)); return item; } void BookmarkBarGtk::ConnectFolderButtonEvents(GtkWidget* widget, bool is_tool_item) { // For toolbar items (i.e. not the overflow button or other bookmarks // button), we handle motion and highlighting manually. gtk_drag_dest_set(widget, is_tool_item ? GTK_DEST_DEFAULT_DROP : GTK_DEST_DEFAULT_ALL, NULL, 0, kDragAction); ui::SetDestTargetList(widget, kDestTargetList); g_signal_connect(widget, "drag-data-received", G_CALLBACK(&OnDragReceivedThunk), this); if (is_tool_item) { g_signal_connect(widget, "drag-motion", G_CALLBACK(&OnFolderDragMotionThunk), this); g_signal_connect(widget, "drag-leave", G_CALLBACK(&OnDragLeaveThunk), this); } g_signal_connect(widget, "button-press-event", G_CALLBACK(OnButtonPressedThunk), this); g_signal_connect(widget, "clicked", G_CALLBACK(OnFolderClickedThunk), this); // Accept middle mouse clicking (which opens all). This must be called after // connecting to "button-press-event" because the handler it attaches stops // the propagation of that signal. gtk_util::SetButtonClickableByMouseButtons(widget, true, true, false); } const BookmarkNode* BookmarkBarGtk::GetNodeForToolButton(GtkWidget* widget) { // First check to see if |button| is special cased. if (widget == other_bookmarks_button_) return model_->other_node(); else if (widget == event_box_.get() || widget == overflow_button_) return model_->bookmark_bar_node(); // Search the contents of |bookmark_toolbar_| for the corresponding widget // and find its index. GtkWidget* item_to_find = gtk_widget_get_parent(widget); int index_to_use = -1; int index = 0; GList* children = gtk_container_get_children( GTK_CONTAINER(bookmark_toolbar_.get())); for (GList* item = children; item; item = item->next, index++) { if (item->data == item_to_find) { index_to_use = index; break; } } g_list_free(children); if (index_to_use != -1) return model_->bookmark_bar_node()->GetChild(index_to_use); return NULL; } void BookmarkBarGtk::PopupMenuForNode(GtkWidget* sender, const BookmarkNode* node, GdkEventButton* event) { if (!model_->IsLoaded()) { // Don't do anything if the model isn't loaded. return; } const BookmarkNode* parent = NULL; std::vector nodes; if (sender == other_bookmarks_button_) { nodes.push_back(node); parent = model_->bookmark_bar_node(); } else if (sender != bookmark_toolbar_.get()) { nodes.push_back(node); parent = node->parent(); } else { parent = model_->bookmark_bar_node(); nodes.push_back(parent); } GtkWindow* window = GTK_WINDOW(gtk_widget_get_toplevel(sender)); current_context_menu_controller_.reset( new BookmarkContextMenuController( window, this, browser_, browser_->profile(), page_navigator_, parent, nodes)); current_context_menu_.reset( new MenuGtk(NULL, current_context_menu_controller_->menu_model())); current_context_menu_->PopupAsContext( gfx::Point(event->x_root, event->y_root), event->time); } gboolean BookmarkBarGtk::OnButtonPressed(GtkWidget* sender, GdkEventButton* event) { last_pressed_coordinates_ = gfx::Point(event->x, event->y); if (event->button == 3 && gtk_widget_get_visible(bookmark_hbox_)) { const BookmarkNode* node = GetNodeForToolButton(sender); DCHECK(node); DCHECK(page_navigator_); PopupMenuForNode(sender, node, event); } return FALSE; } void BookmarkBarGtk::OnClicked(GtkWidget* sender) { const BookmarkNode* node = GetNodeForToolButton(sender); DCHECK(node); DCHECK(node->is_url()); DCHECK(page_navigator_); RecordAppLaunch(browser_->profile(), node->url()); chrome::OpenAll(window_->GetNativeWindow(), page_navigator_, node, event_utils::DispositionForCurrentButtonPressEvent(), browser_->profile()); bookmark_utils::RecordBookmarkLaunch(GetBookmarkLaunchLocation()); } void BookmarkBarGtk::OnButtonDragBegin(GtkWidget* button, GdkDragContext* drag_context) { GtkWidget* button_parent = gtk_widget_get_parent(button); // The parent tool item might be removed during the drag. Ref it so |button| // won't get destroyed. g_object_ref(button_parent); const BookmarkNode* node = GetNodeForToolButton(button); DCHECK(!dragged_node_); dragged_node_ = node; DCHECK(dragged_node_); drag_icon_ = bookmark_utils::GetDragRepresentationForNode( node, model_, theme_service_); // We have to jump through some hoops to get the drag icon to line up because // it is a different size than the button. GtkRequisition req; gtk_widget_size_request(drag_icon_, &req); gfx::Rect button_rect = gtk_util::WidgetBounds(button); gfx::Point drag_icon_relative = gfx::Rect(req.width, req.height).CenterPoint() + (last_pressed_coordinates_ - button_rect.CenterPoint()); gtk_drag_set_icon_widget(drag_context, drag_icon_, drag_icon_relative.x(), drag_icon_relative.y()); // Hide our node, but reserve space for it on the toolbar. int index = gtk_toolbar_get_item_index(GTK_TOOLBAR(bookmark_toolbar_.get()), GTK_TOOL_ITEM(button_parent)); gtk_widget_hide(button); toolbar_drop_item_ = CreateBookmarkToolItem(dragged_node_); g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_)); gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_.get()), GTK_TOOL_ITEM(toolbar_drop_item_), index); // Make sure it stays hidden for the duration of the drag. gtk_widget_set_no_show_all(button, TRUE); } void BookmarkBarGtk::OnButtonDragEnd(GtkWidget* button, GdkDragContext* drag_context) { gtk_widget_show(button); gtk_widget_set_no_show_all(button, FALSE); ClearToolbarDropHighlighting(); DCHECK(dragged_node_); dragged_node_ = NULL; DCHECK(drag_icon_); gtk_widget_destroy(drag_icon_); drag_icon_ = NULL; g_object_unref(gtk_widget_get_parent(button)); } void BookmarkBarGtk::OnButtonDragGet(GtkWidget* widget, GdkDragContext* context, GtkSelectionData* selection_data, guint target_type, guint time) { const BookmarkNode* node = bookmark_utils::BookmarkNodeForWidget(widget); bookmark_utils::WriteBookmarkToSelection(node, selection_data, target_type, browser_->profile()); } void BookmarkBarGtk::OnFolderClicked(GtkWidget* sender) { // Stop its throbbing, if any. HoverControllerGtk* hover_controller = HoverControllerGtk::GetHoverControllerGtk(sender); if (hover_controller) hover_controller->StartThrobbing(0); GdkEvent* event = gtk_get_current_event(); if (event->button.button == 1 || (event->button.button == 2 && sender == overflow_button_)) { bookmark_utils::RecordBookmarkFolderOpen(GetBookmarkLaunchLocation()); PopupForButton(sender); } else if (event->button.button == 2) { const BookmarkNode* node = GetNodeForToolButton(sender); chrome::OpenAll(window_->GetNativeWindow(), page_navigator_, node, NEW_BACKGROUND_TAB, browser_->profile()); } } gboolean BookmarkBarGtk::OnToolbarDragMotion(GtkWidget* toolbar, GdkDragContext* context, gint x, gint y, guint time) { gint index = gtk_toolbar_get_drop_index(GTK_TOOLBAR(toolbar), x, y); return ItemDraggedOverToolbar(context, index, time); } void BookmarkBarGtk::OnToolbarSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) { if (allocation->width == last_allocation_width_) { // If the width hasn't changed, then the visibility of the chevron // doesn't need to change. This check prevents us from getting stuck in a // loop where allocates are queued indefinitely while the visibility of // overflow chevron toggles without actual resizes of the toolbar. return; } last_allocation_width_ = allocation->width; SetChevronState(); } void BookmarkBarGtk::OnDragReceived(GtkWidget* widget, GdkDragContext* context, gint x, gint y, GtkSelectionData* selection_data, guint target_type, guint time) { if (!edit_bookmarks_enabled_.GetValue()) { gtk_drag_finish(context, FALSE, FALSE, time); return; } gboolean dnd_success = FALSE; gboolean delete_selection_data = FALSE; const BookmarkNode* dest_node = model_->bookmark_bar_node(); gint index; if (widget == bookmark_toolbar_.get()) { index = gtk_toolbar_get_drop_index( GTK_TOOLBAR(bookmark_toolbar_.get()), x, y); } else if (widget == instructions_) { dest_node = model_->bookmark_bar_node(); index = 0; } else { index = GetToolbarIndexForDragOverFolder(widget, x); if (index < 0) { dest_node = GetNodeForToolButton(widget); index = dest_node->child_count(); } } switch (target_type) { case ui::CHROME_BOOKMARK_ITEM: { gint length = gtk_selection_data_get_length(selection_data); Pickle pickle(reinterpret_cast( gtk_selection_data_get_data(selection_data)), length); BookmarkNodeData drag_data; if (drag_data.ReadFromPickle(&pickle)) { dnd_success = chrome::DropBookmarks(browser_->profile(), drag_data, dest_node, index) != ui::DragDropTypes::DRAG_NONE; } break; } case ui::CHROME_NAMED_URL: { dnd_success = bookmark_utils::CreateNewBookmarkFromNamedUrl( selection_data, model_, dest_node, index); break; } case ui::TEXT_URI_LIST: { dnd_success = bookmark_utils::CreateNewBookmarksFromURIList( selection_data, model_, dest_node, index); break; } case ui::NETSCAPE_URL: { dnd_success = bookmark_utils::CreateNewBookmarkFromNetscapeURL( selection_data, model_, dest_node, index); break; } case ui::TEXT_PLAIN: { guchar* text = gtk_selection_data_get_text(selection_data); if (!text) break; GURL url(reinterpret_cast(text)); g_free(text); // TODO(estade): It would be nice to head this case off at drag motion, // so that it doesn't look like we can drag onto the bookmark bar. if (!url.is_valid()) break; string16 title = bookmark_utils::GetNameForURL(url); model_->AddURL(dest_node, index, title, url); dnd_success = TRUE; break; } } gtk_drag_finish(context, dnd_success, delete_selection_data, time); } void BookmarkBarGtk::OnDragLeave(GtkWidget* sender, GdkDragContext* context, guint time) { if (GTK_IS_BUTTON(sender)) gtk_drag_unhighlight(sender); ClearToolbarDropHighlighting(); } gboolean BookmarkBarGtk::OnFolderDragMotion(GtkWidget* button, GdkDragContext* context, gint x, gint y, guint time) { if (!edit_bookmarks_enabled_.GetValue()) return FALSE; GdkAtom target_type = gtk_drag_dest_find_target(button, context, NULL); if (target_type == GDK_NONE) return FALSE; int index = GetToolbarIndexForDragOverFolder(button, x); if (index < 0) { ClearToolbarDropHighlighting(); // Drag is over middle of folder. gtk_drag_highlight(button); if (target_type == ui::GetAtomForTarget(ui::CHROME_BOOKMARK_ITEM)) { gdk_drag_status(context, GDK_ACTION_MOVE, time); } else { gdk_drag_status(context, GDK_ACTION_COPY, time); } return TRUE; } // Remove previous highlighting. gtk_drag_unhighlight(button); return ItemDraggedOverToolbar(context, index, time); } gboolean BookmarkBarGtk::OnEventBoxExpose(GtkWidget* widget, GdkEventExpose* event) { TRACE_EVENT0("ui::gtk", "BookmarkBarGtk::OnEventBoxExpose"); GtkThemeService* theme_provider = theme_service_; // We don't need to render the toolbar image in GTK mode, except when // detached. if (theme_provider->UsingNativeTheme() && bookmark_bar_state_ != BookmarkBar::DETACHED) return FALSE; if (bookmark_bar_state_ != BookmarkBar::DETACHED) { cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget)); gdk_cairo_rectangle(cr, &event->area); cairo_clip(cr); // Paint the background theme image. gfx::Point tabstrip_origin = tabstrip_origin_provider_->GetTabStripOriginForWidget(widget); gtk_util::DrawThemedToolbarBackground(widget, cr, event, tabstrip_origin, theme_provider); cairo_destroy(cr); } else { gfx::Size web_contents_size; if (!GetWebContentsSize(&web_contents_size)) return FALSE; gfx::CanvasSkiaPaint canvas(event, true); GtkAllocation allocation; gtk_widget_get_allocation(widget, &allocation); gfx::Rect area = gtk_widget_get_has_window(widget) ? gfx::Rect(0, 0, allocation.width, allocation.height) : gfx::Rect(allocation); NtpBackgroundUtil::PaintBackgroundDetachedMode(theme_provider, &canvas, area, web_contents_size.height()); } return FALSE; // Propagate expose to children. } void BookmarkBarGtk::OnEventBoxDestroy(GtkWidget* widget) { if (model_) model_->RemoveObserver(this); } void BookmarkBarGtk::OnParentSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) { // In detached mode, our layout depends on the size of the tab contents. // We get the size-allocate signal before the tab contents does, hence we // need to post a delayed task so we will paint correctly. Note that // gtk_widget_queue_draw by itself does not work, despite that it claims to // be asynchronous. if (bookmark_bar_state_ == BookmarkBar::DETACHED) { MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&BookmarkBarGtk::PaintEventBox, weak_factory_.GetWeakPtr())); } } void BookmarkBarGtk::OnThrobbingWidgetDestroy(GtkWidget* widget) { SetThrobbingWidget(NULL); } void BookmarkBarGtk::ShowImportDialog() { chrome::ShowImportDialog(browser_); } void BookmarkBarGtk::OnEditBookmarksEnabledChanged() { GtkDestDefaults dest_defaults = *edit_bookmarks_enabled_ ? GTK_DEST_DEFAULT_ALL : GTK_DEST_DEFAULT_DROP; gtk_drag_dest_set(overflow_button_, dest_defaults, NULL, 0, kDragAction); gtk_drag_dest_set(other_bookmarks_button_, dest_defaults, NULL, 0, kDragAction); ui::SetDestTargetList(overflow_button_, kDestTargetList); ui::SetDestTargetList(other_bookmarks_button_, kDestTargetList); }