| // 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 <vector> |
| |
| #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/strings/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/chrome_notification_types.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/core_app_launcher_handler.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/url_constants.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; |
| |
| CoreAppLauncherHandler::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<ThemeService>(theme_service_)); |
| |
| apps_shortcut_visible_.Init( |
| prefs::kShowAppsShortcutInBookmarkBar, |
| browser_->profile()->GetPrefs(), |
| base::Bind(&BookmarkBarGtk::OnAppsPageShortcutVisibilityChanged, |
| base::Unretained(this))); |
| |
| OnAppsPageShortcutVisibilityChanged(); |
| |
| 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_); |
| |
| apps_shortcut_button_ = theme_service_->BuildChromeButton(); |
| ConfigureAppsShortcutButton(apps_shortcut_button_, theme_service_); |
| g_signal_connect(apps_shortcut_button_, "clicked", |
| G_CALLBACK(OnAppsButtonClickedThunk), this); |
| // Accept middle mouse clicking. |
| gtk_util::SetButtonClickableByMouseButtons( |
| apps_shortcut_button_, true, true, false); |
| gtk_box_pack_start(GTK_BOX(bookmark_hbox_), apps_shortcut_button_, |
| FALSE, FALSE, 0); |
| |
| 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<void*>(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_->loaded()) |
| 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<gint>(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<GtkWidget*> 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_->loaded()) |
| 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))); |
| } |
| |
| 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<GtkWidget*>* 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<GtkWidget*>(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<gpointer>(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<gpointer>(&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<gpointer>(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<int>(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)); |
| 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_->loaded()) { |
| // 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(); |
| 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<GdkDragAction>(GDK_ACTION_MOVE | GDK_ACTION_COPY)); |
| int target_mask = 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<void*>(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_->loaded()) { |
| // Don't do anything if the model isn't loaded. |
| return; |
| } |
| |
| const BookmarkNode* parent = NULL; |
| std::vector<const BookmarkNode*> 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_ = 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 = BookmarkNodeForWidget(widget); |
| WriteBookmarkToSelection( |
| node, selection_data, target_type, browser_->profile()); |
| } |
| |
| void BookmarkBarGtk::OnAppsButtonClicked(GtkWidget* sender) { |
| content::OpenURLParams params( |
| GURL(chrome::kChromeUIAppsURL), |
| content::Referrer(), |
| event_utils::DispositionForCurrentButtonPressEvent(), |
| content::PAGE_TRANSITION_AUTO_BOOKMARK, |
| false); |
| browser_->OpenURL(params); |
| bookmark_utils::RecordAppsPageOpen(GetBookmarkLaunchLocation()); |
| } |
| |
| 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()); |
| } |
| gdk_event_free(event); |
| } |
| |
| 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<const char*>( |
| 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 = CreateNewBookmarkFromNamedUrl( |
| selection_data, model_, dest_node, index); |
| break; |
| } |
| |
| case ui::TEXT_URI_LIST: { |
| dnd_success = CreateNewBookmarksFromURIList( |
| selection_data, model_, dest_node, index); |
| break; |
| } |
| |
| case ui::NETSCAPE_URL: { |
| dnd_success = 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<char*>(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 = 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) { |
| base::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::OnAppsPageShortcutVisibilityChanged() { |
| const bool visible = |
| chrome::ShouldShowAppsShortcutInBookmarkBar(browser_->profile()); |
| gtk_widget_set_visible(apps_shortcut_button_, visible); |
| gtk_widget_set_no_show_all(apps_shortcut_button_, !visible); |
| } |
| |
| 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); |
| } |