1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/gtk/bookmarks/bookmark_bar_gtk.h"
10 #include "base/debug/trace_event.h"
11 #include "base/metrics/histogram.h"
12 #include "base/pickle.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/bookmarks/bookmark_model.h"
16 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
17 #include "chrome/browser/bookmarks/bookmark_node_data.h"
18 #include "chrome/browser/bookmarks/bookmark_stats.h"
19 #include "chrome/browser/browser_shutdown.h"
20 #include "chrome/browser/chrome_notification_types.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/themes/theme_properties.h"
24 #include "chrome/browser/ui/bookmarks/bookmark_bar_constants.h"
25 #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
26 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
27 #include "chrome/browser/ui/browser.h"
28 #include "chrome/browser/ui/chrome_pages.h"
29 #include "chrome/browser/ui/gtk/bookmarks/bookmark_bar_instructions_gtk.h"
30 #include "chrome/browser/ui/gtk/bookmarks/bookmark_menu_controller_gtk.h"
31 #include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
32 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
33 #include "chrome/browser/ui/gtk/custom_button.h"
34 #include "chrome/browser/ui/gtk/event_utils.h"
35 #include "chrome/browser/ui/gtk/gtk_chrome_button.h"
36 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
37 #include "chrome/browser/ui/gtk/gtk_util.h"
38 #include "chrome/browser/ui/gtk/hover_controller_gtk.h"
39 #include "chrome/browser/ui/gtk/menu_gtk.h"
40 #include "chrome/browser/ui/gtk/rounded_window.h"
41 #include "chrome/browser/ui/gtk/tabstrip_origin_provider.h"
42 #include "chrome/browser/ui/gtk/view_id_util.h"
43 #include "chrome/browser/ui/ntp_background_util.h"
44 #include "chrome/browser/ui/tabs/tab_strip_model.h"
45 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
46 #include "chrome/common/extensions/extension_constants.h"
47 #include "chrome/common/pref_names.h"
48 #include "chrome/common/url_constants.h"
49 #include "content/public/browser/notification_details.h"
50 #include "content/public/browser/notification_source.h"
51 #include "content/public/browser/user_metrics.h"
52 #include "content/public/browser/web_contents.h"
53 #include "content/public/browser/web_contents_view.h"
54 #include "grit/generated_resources.h"
55 #include "grit/theme_resources.h"
56 #include "grit/ui_resources.h"
57 #include "ui/base/dragdrop/drag_drop_types.h"
58 #include "ui/base/dragdrop/gtk_dnd_util.h"
59 #include "ui/base/l10n/l10n_util.h"
60 #include "ui/base/resource/resource_bundle.h"
61 #include "ui/gfx/canvas_skia_paint.h"
62 #include "ui/gfx/gtk_compat.h"
63 #include "ui/gfx/gtk_util.h"
64 #include "ui/gfx/image/cairo_cached_surface.h"
65 #include "ui/gfx/image/image.h"
67 using base::UserMetricsAction
;
68 using content::PageNavigator
;
69 using content::WebContents
;
73 // Padding for when the bookmark bar is detached.
74 const int kTopBottomNTPPadding
= 12;
75 const int kLeftRightNTPPadding
= 8;
77 // Padding around the bar's content area when the bookmark bar is detached.
78 const int kNTPPadding
= 2;
80 // The number of pixels of rounding on the corners of the bookmark bar content
81 // area when in detached mode.
82 const int kNTPRoundedness
= 3;
84 // The height of the bar when it is "hidden". It is usually not completely
85 // hidden because even when it is closed it forms the bottom few pixels of
87 const int kBookmarkBarMinimumHeight
= 3;
89 // Left-padding for the instructional text.
90 const int kInstructionsPadding
= 6;
92 // Padding around the "Other Bookmarks" button.
93 const int kOtherBookmarksPaddingHorizontal
= 2;
94 const int kOtherBookmarksPaddingVertical
= 1;
96 // The targets accepted by the toolbar and folder buttons for DnD.
97 const int kDestTargetList
[] = { ui::CHROME_BOOKMARK_ITEM
,
101 ui::TEXT_PLAIN
, -1 };
103 // Acceptable drag actions for the bookmark bar drag destinations.
104 const GdkDragAction kDragAction
=
105 GdkDragAction(GDK_ACTION_MOVE
| GDK_ACTION_COPY
);
107 void SetToolBarStyle() {
108 static bool style_was_set
= false;
112 style_was_set
= true;
115 "style \"chrome-bookmark-toolbar\" {"
118 " GtkWidget::focus-padding = 0\n"
119 " GtkContainer::border-width = 0\n"
120 " GtkToolbar::internal-padding = 1\n"
121 " GtkToolbar::shadow-type = GTK_SHADOW_NONE\n"
123 "widget \"*chrome-bookmark-toolbar\" style \"chrome-bookmark-toolbar\"");
126 void RecordAppLaunch(Profile
* profile
, const GURL
& url
) {
127 DCHECK(profile
->GetExtensionService());
128 const extensions::Extension
* extension
=
129 profile
->GetExtensionService()->GetInstalledApp(url
);
133 CoreAppLauncherHandler::RecordAppLaunchType(
134 extension_misc::APP_LAUNCH_BOOKMARK_BAR
,
135 extension
->GetType());
140 BookmarkBarGtk::BookmarkBarGtk(BrowserWindowGtk
* window
,
142 TabstripOriginProvider
* tabstrip_origin_provider
)
143 : page_navigator_(NULL
),
146 tabstrip_origin_provider_(tabstrip_origin_provider
),
151 toolbar_drop_item_(NULL
),
152 theme_service_(GtkThemeService::GetFrom(browser
->profile())),
153 show_instructions_(true),
154 menu_bar_helper_(this),
155 slide_animation_(this),
156 last_allocation_width_(-1),
157 throbbing_widget_(NULL
),
158 bookmark_bar_state_(BookmarkBar::DETACHED
),
160 weak_factory_(this) {
162 // Force an update by simulating being in the wrong state.
163 // BrowserWindowGtk sets our true state after we're created.
164 SetBookmarkBarState(BookmarkBar::SHOW
,
165 BookmarkBar::DONT_ANIMATE_STATE_CHANGE
);
167 registrar_
.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED
,
168 content::Source
<ThemeService
>(theme_service_
));
170 apps_shortcut_visible_
.Init(
171 prefs::kShowAppsShortcutInBookmarkBar
,
172 browser_
->profile()->GetPrefs(),
173 base::Bind(&BookmarkBarGtk::OnAppsPageShortcutVisibilityChanged
,
174 base::Unretained(this)));
176 OnAppsPageShortcutVisibilityChanged();
178 edit_bookmarks_enabled_
.Init(
179 prefs::kEditBookmarksEnabled
,
180 browser_
->profile()->GetPrefs(),
181 base::Bind(&BookmarkBarGtk::OnEditBookmarksEnabledChanged
,
182 base::Unretained(this)));
184 OnEditBookmarksEnabledChanged();
187 BookmarkBarGtk::~BookmarkBarGtk() {
189 bookmark_toolbar_
.Destroy();
190 event_box_
.Destroy();
193 void BookmarkBarGtk::SetPageNavigator(PageNavigator
* navigator
) {
194 page_navigator_
= navigator
;
197 void BookmarkBarGtk::Init() {
198 event_box_
.Own(gtk_event_box_new());
199 g_signal_connect(event_box_
.get(), "destroy",
200 G_CALLBACK(&OnEventBoxDestroyThunk
), this);
201 g_signal_connect(event_box_
.get(), "button-press-event",
202 G_CALLBACK(&OnButtonPressedThunk
), this);
204 ntp_padding_box_
= gtk_alignment_new(0, 0, 1, 1);
205 gtk_container_add(GTK_CONTAINER(event_box_
.get()), ntp_padding_box_
);
207 paint_box_
= gtk_event_box_new();
208 gtk_container_add(GTK_CONTAINER(ntp_padding_box_
), paint_box_
);
209 GdkColor paint_box_color
=
210 theme_service_
->GetGdkColor(ThemeProperties::COLOR_TOOLBAR
);
211 gtk_widget_modify_bg(paint_box_
, GTK_STATE_NORMAL
, &paint_box_color
);
212 gtk_widget_add_events(paint_box_
, GDK_POINTER_MOTION_MASK
|
213 GDK_BUTTON_PRESS_MASK
);
215 bookmark_hbox_
= gtk_hbox_new(FALSE
, 0);
216 gtk_container_add(GTK_CONTAINER(paint_box_
), bookmark_hbox_
);
218 apps_shortcut_button_
= theme_service_
->BuildChromeButton();
219 ConfigureAppsShortcutButton(apps_shortcut_button_
, theme_service_
);
220 g_signal_connect(apps_shortcut_button_
, "clicked",
221 G_CALLBACK(OnAppsButtonClickedThunk
), this);
222 // Accept middle mouse clicking.
223 gtk_util::SetButtonClickableByMouseButtons(
224 apps_shortcut_button_
, true, true, false);
225 gtk_box_pack_start(GTK_BOX(bookmark_hbox_
), apps_shortcut_button_
,
228 instructions_
= gtk_alignment_new(0, 0, 1, 1);
229 gtk_alignment_set_padding(GTK_ALIGNMENT(instructions_
), 0, 0,
230 kInstructionsPadding
, 0);
231 Profile
* profile
= browser_
->profile();
232 instructions_gtk_
.reset(new BookmarkBarInstructionsGtk(this, profile
));
233 gtk_container_add(GTK_CONTAINER(instructions_
), instructions_gtk_
->widget());
234 gtk_box_pack_start(GTK_BOX(bookmark_hbox_
), instructions_
,
237 gtk_drag_dest_set(instructions_
,
238 GtkDestDefaults(GTK_DEST_DEFAULT_DROP
| GTK_DEST_DEFAULT_MOTION
),
239 NULL
, 0, kDragAction
);
240 ui::SetDestTargetList(instructions_
, kDestTargetList
);
241 g_signal_connect(instructions_
, "drag-data-received",
242 G_CALLBACK(&OnDragReceivedThunk
), this);
244 g_signal_connect(event_box_
.get(), "expose-event",
245 G_CALLBACK(&OnEventBoxExposeThunk
), this);
246 UpdateEventBoxPaintability();
248 bookmark_toolbar_
.Own(gtk_toolbar_new());
250 gtk_widget_set_name(bookmark_toolbar_
.get(), "chrome-bookmark-toolbar");
251 gtk_util::SuppressDefaultPainting(bookmark_toolbar_
.get());
252 g_signal_connect(bookmark_toolbar_
.get(), "size-allocate",
253 G_CALLBACK(&OnToolbarSizeAllocateThunk
), this);
254 gtk_box_pack_start(GTK_BOX(bookmark_hbox_
), bookmark_toolbar_
.get(),
257 overflow_button_
= theme_service_
->BuildChromeButton();
258 g_object_set_data(G_OBJECT(overflow_button_
), "left-align-popup",
259 reinterpret_cast<void*>(true));
260 SetOverflowButtonAppearance();
261 ConnectFolderButtonEvents(overflow_button_
, false);
262 gtk_box_pack_start(GTK_BOX(bookmark_hbox_
), overflow_button_
,
265 gtk_drag_dest_set(bookmark_toolbar_
.get(), GTK_DEST_DEFAULT_DROP
,
266 NULL
, 0, kDragAction
);
267 ui::SetDestTargetList(bookmark_toolbar_
.get(), kDestTargetList
);
268 g_signal_connect(bookmark_toolbar_
.get(), "drag-motion",
269 G_CALLBACK(&OnToolbarDragMotionThunk
), this);
270 g_signal_connect(bookmark_toolbar_
.get(), "drag-leave",
271 G_CALLBACK(&OnDragLeaveThunk
), this);
272 g_signal_connect(bookmark_toolbar_
.get(), "drag-data-received",
273 G_CALLBACK(&OnDragReceivedThunk
), this);
275 other_bookmarks_separator_
= theme_service_
->CreateToolbarSeparator();
276 gtk_box_pack_start(GTK_BOX(bookmark_hbox_
), other_bookmarks_separator_
,
279 // We pack the button manually (rather than using gtk_button_set_*) so that
280 // we can have finer control over its label.
281 other_bookmarks_button_
= theme_service_
->BuildChromeButton();
282 gtk_widget_show_all(other_bookmarks_button_
);
283 ConnectFolderButtonEvents(other_bookmarks_button_
, false);
284 other_padding_
= gtk_alignment_new(0, 0, 1, 1);
285 gtk_alignment_set_padding(GTK_ALIGNMENT(other_padding_
),
286 kOtherBookmarksPaddingVertical
,
287 kOtherBookmarksPaddingVertical
,
288 kOtherBookmarksPaddingHorizontal
,
289 kOtherBookmarksPaddingHorizontal
);
290 gtk_container_add(GTK_CONTAINER(other_padding_
), other_bookmarks_button_
);
291 gtk_box_pack_start(GTK_BOX(bookmark_hbox_
), other_padding_
,
293 gtk_widget_set_no_show_all(other_padding_
, TRUE
);
295 gtk_widget_set_size_request(event_box_
.get(), -1, kBookmarkBarMinimumHeight
);
297 ViewIDUtil::SetID(other_bookmarks_button_
, VIEW_ID_OTHER_BOOKMARKS
);
298 ViewIDUtil::SetID(widget(), VIEW_ID_BOOKMARK_BAR
);
300 gtk_widget_show_all(widget());
301 gtk_widget_hide(widget());
304 // TODO(erg): Handle extensions
305 model_
= BookmarkModelFactory::GetForProfile(profile
);
306 model_
->AddObserver(this);
307 if (model_
->loaded())
308 BookmarkModelLoaded(model_
, false);
309 // else case: we'll receive notification back from the BookmarkModel when done
310 // loading, then we'll populate the bar.
313 void BookmarkBarGtk::SetBookmarkBarState(
314 BookmarkBar::State state
,
315 BookmarkBar::AnimateChangeType animate_type
) {
316 TRACE_EVENT0("ui::gtk", "BookmarkBarGtk::SetBookmarkBarState");
317 if (animate_type
== BookmarkBar::ANIMATE_STATE_CHANGE
&&
318 (state
== BookmarkBar::DETACHED
||
319 bookmark_bar_state_
== BookmarkBar::DETACHED
)) {
320 // TODO(estade): animate the transition between detached and non or remove
321 // detached entirely.
322 animate_type
= BookmarkBar::DONT_ANIMATE_STATE_CHANGE
;
324 BookmarkBar::State old_state
= bookmark_bar_state_
;
325 bookmark_bar_state_
= state
;
326 if (state
== BookmarkBar::SHOW
|| state
== BookmarkBar::DETACHED
)
327 Show(old_state
, animate_type
);
329 Hide(old_state
, animate_type
);
332 int BookmarkBarGtk::GetHeight() {
333 GtkAllocation allocation
;
334 gtk_widget_get_allocation(event_box_
.get(), &allocation
);
335 return allocation
.height
- kBookmarkBarMinimumHeight
;
338 bool BookmarkBarGtk::IsAnimating() {
339 return slide_animation_
.is_animating();
342 void BookmarkBarGtk::CalculateMaxHeight() {
343 if (theme_service_
->UsingNativeTheme()) {
344 // Get the requisition of our single child instead of the event box itself
345 // because the event box probably already has a size request.
347 gtk_widget_size_request(ntp_padding_box_
, &req
);
348 max_height_
= req
.height
;
350 max_height_
= (bookmark_bar_state_
== BookmarkBar::DETACHED
) ?
351 chrome::kNTPBookmarkBarHeight
: chrome::kBookmarkBarHeight
;
355 void BookmarkBarGtk::AnimationProgressed(const gfx::Animation
* animation
) {
356 DCHECK_EQ(animation
, &slide_animation_
);
359 static_cast<gint
>(animation
->GetCurrentValue() *
360 (max_height_
- kBookmarkBarMinimumHeight
)) +
361 kBookmarkBarMinimumHeight
;
362 gtk_widget_set_size_request(event_box_
.get(), -1, height
);
365 void BookmarkBarGtk::AnimationEnded(const gfx::Animation
* animation
) {
366 DCHECK_EQ(animation
, &slide_animation_
);
368 if (!slide_animation_
.IsShowing()) {
369 gtk_widget_hide(bookmark_hbox_
);
371 // We can be windowless during unit tests.
373 // Because of our constant resizing and our toolbar/bookmark bar overlap
374 // shenanigans, gtk+ gets confused, partially draws parts of the bookmark
375 // bar into the toolbar and than doesn't queue a redraw to fix it. So do
376 // it manually by telling the toolbar area to redraw itself.
377 window_
->QueueToolbarRedraw();
382 // MenuBarHelper::Delegate implementation --------------------------------------
383 void BookmarkBarGtk::PopupForButton(GtkWidget
* button
) {
384 const BookmarkNode
* node
= GetNodeForToolButton(button
);
386 DCHECK(page_navigator_
);
388 int first_hidden
= GetFirstHiddenBookmark(0, NULL
);
389 if (first_hidden
== -1) {
390 // No overflow exists: don't show anything for the overflow button.
391 if (button
== overflow_button_
)
394 // Overflow exists: don't show anything for an overflowed folder button.
395 if (button
!= overflow_button_
&& button
!= other_bookmarks_button_
&&
396 node
->parent()->GetIndexOf(node
) >= first_hidden
) {
402 new BookmarkMenuController(browser_
, page_navigator_
,
403 GTK_WINDOW(gtk_widget_get_toplevel(button
)),
405 button
== overflow_button_
? first_hidden
: 0));
406 menu_bar_helper_
.MenuStartedShowing(button
, current_menu_
->widget());
407 GdkEvent
* event
= gtk_get_current_event();
408 current_menu_
->Popup(button
, event
->button
.button
, event
->button
.time
);
409 gdk_event_free(event
);
412 void BookmarkBarGtk::PopupForButtonNextTo(GtkWidget
* button
,
413 GtkMenuDirectionType dir
) {
414 const BookmarkNode
* relative_node
= GetNodeForToolButton(button
);
415 DCHECK(relative_node
);
417 // Find out the order of the buttons.
418 std::vector
<GtkWidget
*> folder_list
;
419 const int first_hidden
= GetFirstHiddenBookmark(0, &folder_list
);
420 if (first_hidden
!= -1)
421 folder_list
.push_back(overflow_button_
);
423 if (!model_
->other_node()->empty())
424 folder_list
.push_back(other_bookmarks_button_
);
426 // Find the position of |button|.
428 for (size_t i
= 0; i
< folder_list
.size(); ++i
) {
429 if (folder_list
[i
] == button
) {
434 DCHECK_NE(button_idx
, -1);
436 // Find the GtkWidget* for the actual target button.
437 int shift
= dir
== GTK_MENU_DIR_PARENT
? -1 : 1;
438 button_idx
= (button_idx
+ shift
+ folder_list
.size()) % folder_list
.size();
439 PopupForButton(folder_list
[button_idx
]);
442 void BookmarkBarGtk::CloseMenu() {
443 current_context_menu_
->Cancel();
446 void BookmarkBarGtk::Show(BookmarkBar::State old_state
,
447 BookmarkBar::AnimateChangeType animate_type
) {
448 gtk_widget_show_all(widget());
449 UpdateDetachedState(old_state
);
450 CalculateMaxHeight();
451 if (animate_type
== BookmarkBar::ANIMATE_STATE_CHANGE
) {
452 slide_animation_
.Show();
454 slide_animation_
.Reset(1);
455 AnimationProgressed(&slide_animation_
);
458 if (model_
&& model_
->loaded())
459 UpdateOtherBookmarksVisibility();
461 // Hide out behind the findbar. This is rather fragile code, it could
462 // probably be improved.
463 if (bookmark_bar_state_
== BookmarkBar::DETACHED
) {
464 if (theme_service_
->UsingNativeTheme()) {
465 GtkWidget
* parent
= gtk_widget_get_parent(event_box_
.get());
466 if (gtk_widget_get_realized(parent
))
467 gdk_window_lower(gtk_widget_get_window(parent
));
468 if (gtk_widget_get_realized(event_box_
.get()))
469 gdk_window_lower(gtk_widget_get_window(event_box_
.get()));
470 } else { // Chromium theme mode.
471 if (gtk_widget_get_realized(paint_box_
)) {
472 gdk_window_lower(gtk_widget_get_window(paint_box_
));
473 // The event box won't stay below its children's GdkWindows unless we
474 // toggle the above-child property here. If the event box doesn't stay
475 // below its children then events will be routed to it rather than the
477 gtk_event_box_set_above_child(GTK_EVENT_BOX(event_box_
.get()), TRUE
);
478 gtk_event_box_set_above_child(GTK_EVENT_BOX(event_box_
.get()), FALSE
);
483 // Maybe show the instructions
484 gtk_widget_set_visible(bookmark_toolbar_
.get(), !show_instructions_
);
485 gtk_widget_set_visible(instructions_
, show_instructions_
);
490 void BookmarkBarGtk::Hide(BookmarkBar::State old_state
,
491 BookmarkBar::AnimateChangeType animate_type
) {
492 UpdateDetachedState(old_state
);
494 // After coming out of fullscreen, the browser window sets the bookmark bar
495 // to the "hidden" state, which means we need to show our minimum height.
496 if (!window_
->IsFullscreen())
497 gtk_widget_show(widget());
498 CalculateMaxHeight();
499 // Sometimes we get called without a matching call to open. If that happens
501 if (slide_animation_
.IsShowing() &&
502 animate_type
== BookmarkBar::ANIMATE_STATE_CHANGE
) {
503 slide_animation_
.Hide();
505 gtk_widget_hide(bookmark_hbox_
);
506 slide_animation_
.Reset(0);
507 AnimationProgressed(&slide_animation_
);
511 void BookmarkBarGtk::SetInstructionState() {
513 show_instructions_
= model_
->bookmark_bar_node()->empty();
515 gtk_widget_set_visible(bookmark_toolbar_
.get(), !show_instructions_
);
516 gtk_widget_set_visible(instructions_
, show_instructions_
);
519 void BookmarkBarGtk::SetChevronState() {
520 if (!gtk_widget_get_visible(bookmark_hbox_
))
523 if (show_instructions_
) {
524 gtk_widget_hide(overflow_button_
);
529 if (gtk_widget_get_visible(overflow_button_
)) {
530 GtkAllocation allocation
;
531 gtk_widget_get_allocation(overflow_button_
, &allocation
);
532 extra_space
= allocation
.width
;
535 int overflow_idx
= GetFirstHiddenBookmark(extra_space
, NULL
);
536 if (overflow_idx
== -1)
537 gtk_widget_hide(overflow_button_
);
539 gtk_widget_show_all(overflow_button_
);
542 void BookmarkBarGtk::UpdateOtherBookmarksVisibility() {
543 bool has_other_children
= !model_
->other_node()->empty();
545 gtk_widget_set_visible(other_padding_
, has_other_children
);
546 gtk_widget_set_visible(other_bookmarks_separator_
, has_other_children
);
549 void BookmarkBarGtk::RemoveAllButtons() {
550 gtk_util::RemoveAllChildren(bookmark_toolbar_
.get());
551 menu_bar_helper_
.Clear();
554 void BookmarkBarGtk::AddCoreButtons() {
555 menu_bar_helper_
.Add(other_bookmarks_button_
);
556 menu_bar_helper_
.Add(overflow_button_
);
559 void BookmarkBarGtk::ResetButtons() {
563 const BookmarkNode
* bar
= model_
->bookmark_bar_node();
564 DCHECK(bar
&& model_
->other_node());
566 // Create a button for each of the children on the bookmark bar.
567 for (int i
= 0; i
< bar
->child_count(); ++i
) {
568 const BookmarkNode
* node
= bar
->GetChild(i
);
569 GtkToolItem
* item
= CreateBookmarkToolItem(node
);
570 gtk_toolbar_insert(GTK_TOOLBAR(bookmark_toolbar_
.get()), item
, -1);
571 if (node
->is_folder())
572 menu_bar_helper_
.Add(gtk_bin_get_child(GTK_BIN(item
)));
575 ConfigureButtonForNode(
576 model_
->other_node(), model_
, other_bookmarks_button_
, theme_service_
);
578 SetInstructionState();
582 int BookmarkBarGtk::GetBookmarkButtonCount() {
583 GList
* children
= gtk_container_get_children(
584 GTK_CONTAINER(bookmark_toolbar_
.get()));
585 int count
= g_list_length(children
);
586 g_list_free(children
);
590 BookmarkLaunchLocation
BookmarkBarGtk::GetBookmarkLaunchLocation() const {
591 return bookmark_bar_state_
== BookmarkBar::DETACHED
?
592 BOOKMARK_LAUNCH_LOCATION_DETACHED_BAR
:
593 BOOKMARK_LAUNCH_LOCATION_ATTACHED_BAR
;
596 void BookmarkBarGtk::SetOverflowButtonAppearance() {
597 GtkWidget
* former_child
= gtk_bin_get_child(GTK_BIN(overflow_button_
));
599 gtk_widget_destroy(former_child
);
601 GtkWidget
* new_child
;
602 if (theme_service_
->UsingNativeTheme()) {
603 new_child
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
605 const gfx::Image
& image
= ui::ResourceBundle::GetSharedInstance().
606 GetNativeImageNamed(IDR_BOOKMARK_BAR_CHEVRONS
,
607 ui::ResourceBundle::RTL_ENABLED
);
608 new_child
= gtk_image_new_from_pixbuf(image
.ToGdkPixbuf());
611 gtk_container_add(GTK_CONTAINER(overflow_button_
), new_child
);
615 int BookmarkBarGtk::GetFirstHiddenBookmark(int extra_space
,
616 std::vector
<GtkWidget
*>* showing_folders
) {
618 // We're going to keep track of how much width we've used as we move along
619 // the bookmark bar. If we ever surpass the width of the bookmark bar, we'll
620 // know that's the first hidden bookmark.
622 // GTK appears to require one pixel of padding to the side of the first and
623 // last buttons on the bar.
624 // TODO(gideonwald): figure out the precise source of these extra two pixels
625 // and make this calculation more reliable.
626 GtkAllocation allocation
;
627 gtk_widget_get_allocation(bookmark_toolbar_
.get(), &allocation
);
628 int total_width
= allocation
.width
- 2;
629 bool overflow
= false;
630 GtkRequisition requested_size_
;
631 GList
* toolbar_items
=
632 gtk_container_get_children(GTK_CONTAINER(bookmark_toolbar_
.get()));
633 for (GList
* iter
= toolbar_items
; iter
; iter
= g_list_next(iter
)) {
634 GtkWidget
* tool_item
= reinterpret_cast<GtkWidget
*>(iter
->data
);
635 gtk_widget_size_request(tool_item
, &requested_size_
);
636 width_used
+= requested_size_
.width
;
637 // |extra_space| is available if we can remove the chevron, which happens
638 // only if there are no more potential overflow bookmarks after this one.
639 overflow
= width_used
> total_width
+ (g_list_next(iter
) ? 0 : extra_space
);
643 if (showing_folders
&&
644 model_
->bookmark_bar_node()->GetChild(rv
)->is_folder()) {
645 showing_folders
->push_back(gtk_bin_get_child(GTK_BIN(tool_item
)));
650 g_list_free(toolbar_items
);
658 void BookmarkBarGtk::UpdateDetachedState(BookmarkBar::State old_state
) {
659 bool old_detached
= old_state
== BookmarkBar::DETACHED
;
660 bool detached
= bookmark_bar_state_
== BookmarkBar::DETACHED
;
661 if (detached
== old_detached
)
665 gtk_event_box_set_visible_window(GTK_EVENT_BOX(paint_box_
), TRUE
);
666 GdkColor stroke_color
= theme_service_
->UsingNativeTheme() ?
667 theme_service_
->GetBorderColor() :
668 theme_service_
->GetGdkColor(ThemeProperties::COLOR_NTP_HEADER
);
669 gtk_util::ActAsRoundedWindow(paint_box_
, stroke_color
, kNTPRoundedness
,
670 gtk_util::ROUNDED_ALL
, gtk_util::BORDER_ALL
);
672 gtk_alignment_set_padding(GTK_ALIGNMENT(ntp_padding_box_
),
673 kTopBottomNTPPadding
, kTopBottomNTPPadding
,
674 kLeftRightNTPPadding
, kLeftRightNTPPadding
);
675 gtk_container_set_border_width(GTK_CONTAINER(bookmark_hbox_
), kNTPPadding
);
677 gtk_util::StopActingAsRoundedWindow(paint_box_
);
678 gtk_event_box_set_visible_window(GTK_EVENT_BOX(paint_box_
), FALSE
);
679 gtk_alignment_set_padding(GTK_ALIGNMENT(ntp_padding_box_
), 0, 0, 0, 0);
680 gtk_container_set_border_width(GTK_CONTAINER(bookmark_hbox_
), 0);
683 UpdateEventBoxPaintability();
684 // |window_| can be NULL during testing.
685 // Listen for parent size allocations. Only connect once.
686 if (window_
&& detached
) {
687 GtkWidget
* parent
= gtk_widget_get_parent(widget());
689 g_signal_handler_find(parent
, G_SIGNAL_MATCH_FUNC
,
690 0, 0, NULL
, reinterpret_cast<gpointer
>(OnParentSizeAllocateThunk
),
692 g_signal_connect(parent
, "size-allocate",
693 G_CALLBACK(OnParentSizeAllocateThunk
), this);
698 void BookmarkBarGtk::UpdateEventBoxPaintability() {
699 gtk_widget_set_app_paintable(
701 (!theme_service_
->UsingNativeTheme() ||
702 bookmark_bar_state_
== BookmarkBar::DETACHED
));
703 // When using the GTK+ theme, we need to have the event box be visible so
704 // buttons don't get a halo color from the background. When using Chromium
705 // themes, we want to let the background show through the toolbar.
707 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_
.get()),
708 theme_service_
->UsingNativeTheme());
711 void BookmarkBarGtk::PaintEventBox() {
712 gfx::Size web_contents_size
;
713 if (GetWebContentsSize(&web_contents_size
) &&
714 web_contents_size
!= last_web_contents_size_
) {
715 last_web_contents_size_
= web_contents_size
;
716 gtk_widget_queue_draw(event_box_
.get());
720 bool BookmarkBarGtk::GetWebContentsSize(gfx::Size
* size
) {
721 Browser
* browser
= browser_
;
726 WebContents
* web_contents
=
727 browser
->tab_strip_model()->GetActiveWebContents();
729 // It is possible to have a browser but no WebContents while under testing,
730 // so don't NOTREACHED() and error the program.
733 if (!web_contents
->GetView()) {
737 *size
= web_contents
->GetView()->GetContainerSize();
741 void BookmarkBarGtk::StartThrobbingAfterAllocation(GtkWidget
* item
) {
742 g_signal_connect_after(
743 item
, "size-allocate", G_CALLBACK(OnItemAllocateThunk
), this);
746 void BookmarkBarGtk::OnItemAllocate(GtkWidget
* item
,
747 GtkAllocation
* allocation
) {
748 // We only want to fire on the item's first allocation.
749 g_signal_handlers_disconnect_by_func(
750 item
, reinterpret_cast<gpointer
>(&OnItemAllocateThunk
), this);
752 GtkWidget
* button
= gtk_bin_get_child(GTK_BIN(item
));
753 const BookmarkNode
* node
= GetNodeForToolButton(button
);
755 StartThrobbing(node
);
758 void BookmarkBarGtk::StartThrobbing(const BookmarkNode
* node
) {
759 const BookmarkNode
* parent_on_bb
= NULL
;
760 for (const BookmarkNode
* parent
= node
; parent
;
761 parent
= parent
->parent()) {
762 if (parent
->parent() == model_
->bookmark_bar_node()) {
763 parent_on_bb
= parent
;
768 GtkWidget
* widget_to_throb
= NULL
;
771 // Descendant of "Other Bookmarks".
772 widget_to_throb
= other_bookmarks_button_
;
774 int hidden
= GetFirstHiddenBookmark(0, NULL
);
775 int idx
= model_
->bookmark_bar_node()->GetIndexOf(parent_on_bb
);
777 if (hidden
>= 0 && hidden
<= idx
) {
778 widget_to_throb
= overflow_button_
;
780 widget_to_throb
= gtk_bin_get_child(GTK_BIN(gtk_toolbar_get_nth_item(
781 GTK_TOOLBAR(bookmark_toolbar_
.get()), idx
)));
785 SetThrobbingWidget(widget_to_throb
);
788 void BookmarkBarGtk::SetThrobbingWidget(GtkWidget
* widget
) {
789 if (throbbing_widget_
) {
790 HoverControllerGtk
* hover_controller
=
791 HoverControllerGtk::GetHoverControllerGtk(throbbing_widget_
);
792 if (hover_controller
)
793 hover_controller
->StartThrobbing(0);
795 g_signal_handlers_disconnect_by_func(
797 reinterpret_cast<gpointer
>(OnThrobbingWidgetDestroyThunk
),
799 g_object_unref(throbbing_widget_
);
800 throbbing_widget_
= NULL
;
804 throbbing_widget_
= widget
;
805 g_object_ref(throbbing_widget_
);
806 g_signal_connect(throbbing_widget_
, "destroy",
807 G_CALLBACK(OnThrobbingWidgetDestroyThunk
), this);
809 HoverControllerGtk
* hover_controller
=
810 HoverControllerGtk::GetHoverControllerGtk(throbbing_widget_
);
811 if (hover_controller
)
812 hover_controller
->StartThrobbing(4);
816 gboolean
BookmarkBarGtk::ItemDraggedOverToolbar(GdkDragContext
* context
,
819 if (!edit_bookmarks_enabled_
.GetValue())
821 GdkAtom target_type
=
822 gtk_drag_dest_find_target(bookmark_toolbar_
.get(), context
, NULL
);
823 if (target_type
== GDK_NONE
) {
824 // We shouldn't act like a drop target when something that we can't deal
825 // with is dragged over the toolbar.
829 if (!toolbar_drop_item_
) {
831 toolbar_drop_item_
= CreateBookmarkToolItem(dragged_node_
);
832 g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_
));
834 // Create a fake item the size of other_node().
836 // TODO(erg): Maybe somehow figure out the real size for the drop target?
838 CreateBookmarkToolItem(model_
->other_node());
839 g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_
));
843 gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_
.get()),
844 GTK_TOOL_ITEM(toolbar_drop_item_
),
846 if (target_type
== ui::GetAtomForTarget(ui::CHROME_BOOKMARK_ITEM
)) {
847 gdk_drag_status(context
, GDK_ACTION_MOVE
, time
);
849 gdk_drag_status(context
, GDK_ACTION_COPY
, time
);
855 int BookmarkBarGtk::GetToolbarIndexForDragOverFolder(GtkWidget
* button
,
857 GtkAllocation allocation
;
858 gtk_widget_get_allocation(button
, &allocation
);
860 int margin
= std::min(15, static_cast<int>(0.3 * allocation
.width
));
861 if (x
> margin
&& x
< (allocation
.width
- margin
))
864 GtkWidget
* parent
= gtk_widget_get_parent(button
);
865 gint index
= gtk_toolbar_get_item_index(GTK_TOOLBAR(bookmark_toolbar_
.get()),
866 GTK_TOOL_ITEM(parent
));
872 void BookmarkBarGtk::ClearToolbarDropHighlighting() {
873 if (toolbar_drop_item_
) {
874 g_object_unref(toolbar_drop_item_
);
875 toolbar_drop_item_
= NULL
;
878 gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_
.get()),
882 void BookmarkBarGtk::BookmarkModelLoaded(BookmarkModel
* model
,
883 bool ids_reassigned
) {
884 // If |instructions_| has been nulled, we are in the middle of browser
885 // shutdown. Do nothing.
889 UpdateOtherBookmarksVisibility();
893 void BookmarkBarGtk::BookmarkModelBeingDeleted(BookmarkModel
* model
) {
894 // The bookmark model should never be deleted before us. This code exists
895 // to check for regressions in shutdown code and not crash.
896 if (!browser_shutdown::ShuttingDownWithoutClosingBrowsers())
899 // Do minimal cleanup, presumably we'll be deleted shortly.
900 model_
->RemoveObserver(this);
904 void BookmarkBarGtk::BookmarkNodeMoved(BookmarkModel
* model
,
905 const BookmarkNode
* old_parent
,
907 const BookmarkNode
* new_parent
,
909 const BookmarkNode
* node
= new_parent
->GetChild(new_index
);
910 BookmarkNodeRemoved(model
, old_parent
, old_index
, node
);
911 BookmarkNodeAdded(model
, new_parent
, new_index
);
914 void BookmarkBarGtk::BookmarkNodeAdded(BookmarkModel
* model
,
915 const BookmarkNode
* parent
,
917 UpdateOtherBookmarksVisibility();
919 const BookmarkNode
* node
= parent
->GetChild(index
);
920 if (parent
!= model_
->bookmark_bar_node()) {
921 StartThrobbing(node
);
924 DCHECK(index
>= 0 && index
<= GetBookmarkButtonCount());
926 GtkToolItem
* item
= CreateBookmarkToolItem(node
);
927 gtk_toolbar_insert(GTK_TOOLBAR(bookmark_toolbar_
.get()),
929 if (node
->is_folder())
930 menu_bar_helper_
.Add(gtk_bin_get_child(GTK_BIN(item
)));
932 SetInstructionState();
935 StartThrobbingAfterAllocation(GTK_WIDGET(item
));
938 void BookmarkBarGtk::BookmarkNodeRemoved(BookmarkModel
* model
,
939 const BookmarkNode
* parent
,
941 const BookmarkNode
* node
) {
942 UpdateOtherBookmarksVisibility();
944 if (parent
!= model_
->bookmark_bar_node()) {
945 // We only care about nodes on the bookmark bar.
948 DCHECK(old_index
>= 0 && old_index
< GetBookmarkButtonCount());
950 GtkWidget
* to_remove
= GTK_WIDGET(gtk_toolbar_get_nth_item(
951 GTK_TOOLBAR(bookmark_toolbar_
.get()), old_index
));
952 if (node
->is_folder())
953 menu_bar_helper_
.Remove(gtk_bin_get_child(GTK_BIN(to_remove
)));
954 gtk_container_remove(GTK_CONTAINER(bookmark_toolbar_
.get()),
957 SetInstructionState();
961 void BookmarkBarGtk::BookmarkAllNodesRemoved(BookmarkModel
* model
) {
962 UpdateOtherBookmarksVisibility();
966 void BookmarkBarGtk::BookmarkNodeChanged(BookmarkModel
* model
,
967 const BookmarkNode
* node
) {
968 if (node
->parent() != model_
->bookmark_bar_node()) {
969 // We only care about nodes on the bookmark bar.
972 int index
= model_
->bookmark_bar_node()->GetIndexOf(node
);
975 GtkToolItem
* item
= gtk_toolbar_get_nth_item(
976 GTK_TOOLBAR(bookmark_toolbar_
.get()), index
);
977 GtkWidget
* button
= gtk_bin_get_child(GTK_BIN(item
));
978 ConfigureButtonForNode(node
, model
, button
, theme_service_
);
982 void BookmarkBarGtk::BookmarkNodeFaviconChanged(BookmarkModel
* model
,
983 const BookmarkNode
* node
) {
984 BookmarkNodeChanged(model
, node
);
987 void BookmarkBarGtk::BookmarkNodeChildrenReordered(BookmarkModel
* model
,
988 const BookmarkNode
* node
) {
989 if (node
!= model_
->bookmark_bar_node())
990 return; // We only care about reordering of the bookmark bar node.
995 void BookmarkBarGtk::Observe(int type
,
996 const content::NotificationSource
& source
,
997 const content::NotificationDetails
& details
) {
998 if (type
== chrome::NOTIFICATION_BROWSER_THEME_CHANGED
) {
999 if (model_
&& model_
->loaded()) {
1000 // Regenerate the bookmark bar with all new objects with their theme
1001 // properties set correctly for the new theme.
1005 // Resize the bookmark bar since the target height may have changed.
1006 CalculateMaxHeight();
1007 AnimationProgressed(&slide_animation_
);
1009 UpdateEventBoxPaintability();
1011 GdkColor paint_box_color
=
1012 theme_service_
->GetGdkColor(ThemeProperties::COLOR_TOOLBAR
);
1013 gtk_widget_modify_bg(paint_box_
, GTK_STATE_NORMAL
, &paint_box_color
);
1015 if (bookmark_bar_state_
== BookmarkBar::DETACHED
) {
1016 GdkColor stroke_color
= theme_service_
->UsingNativeTheme() ?
1017 theme_service_
->GetBorderColor() :
1018 theme_service_
->GetGdkColor(ThemeProperties::COLOR_NTP_HEADER
);
1019 gtk_util::SetRoundedWindowBorderColor(paint_box_
, stroke_color
);
1022 SetOverflowButtonAppearance();
1026 GtkWidget
* BookmarkBarGtk::CreateBookmarkButton(const BookmarkNode
* node
) {
1027 GtkWidget
* button
= theme_service_
->BuildChromeButton();
1028 ConfigureButtonForNode(node
, model_
, button
, theme_service_
);
1030 // The tool item is also a source for dragging
1031 gtk_drag_source_set(button
, GDK_BUTTON1_MASK
, NULL
, 0,
1032 static_cast<GdkDragAction
>(GDK_ACTION_MOVE
| GDK_ACTION_COPY
));
1033 int target_mask
= GetCodeMask(node
->is_folder());
1034 ui::SetSourceTargetListFromCodeMask(button
, target_mask
);
1035 g_signal_connect(button
, "drag-begin",
1036 G_CALLBACK(&OnButtonDragBeginThunk
), this);
1037 g_signal_connect(button
, "drag-end",
1038 G_CALLBACK(&OnButtonDragEndThunk
), this);
1039 g_signal_connect(button
, "drag-data-get",
1040 G_CALLBACK(&OnButtonDragGetThunk
), this);
1041 // We deliberately don't connect to "drag-data-delete" because the action of
1042 // moving a button will regenerate all the contents of the bookmarks bar
1045 if (node
->is_url()) {
1046 // Connect to 'button-release-event' instead of 'clicked' because we need
1047 // access to the modifier keys and we do different things on each
1049 g_signal_connect(button
, "button-press-event",
1050 G_CALLBACK(OnButtonPressedThunk
), this);
1051 g_signal_connect(button
, "clicked",
1052 G_CALLBACK(OnClickedThunk
), this);
1053 gtk_util::SetButtonTriggersNavigation(button
);
1055 ConnectFolderButtonEvents(button
, true);
1061 GtkToolItem
* BookmarkBarGtk::CreateBookmarkToolItem(const BookmarkNode
* node
) {
1062 GtkWidget
* button
= CreateBookmarkButton(node
);
1063 g_object_set_data(G_OBJECT(button
), "left-align-popup",
1064 reinterpret_cast<void*>(true));
1066 GtkToolItem
* item
= gtk_tool_item_new();
1067 gtk_container_add(GTK_CONTAINER(item
), button
);
1068 gtk_widget_show_all(GTK_WIDGET(item
));
1073 void BookmarkBarGtk::ConnectFolderButtonEvents(GtkWidget
* widget
,
1074 bool is_tool_item
) {
1075 // For toolbar items (i.e. not the overflow button or other bookmarks
1076 // button), we handle motion and highlighting manually.
1077 gtk_drag_dest_set(widget
,
1078 is_tool_item
? GTK_DEST_DEFAULT_DROP
:
1079 GTK_DEST_DEFAULT_ALL
,
1083 ui::SetDestTargetList(widget
, kDestTargetList
);
1084 g_signal_connect(widget
, "drag-data-received",
1085 G_CALLBACK(&OnDragReceivedThunk
), this);
1087 g_signal_connect(widget
, "drag-motion",
1088 G_CALLBACK(&OnFolderDragMotionThunk
), this);
1089 g_signal_connect(widget
, "drag-leave",
1090 G_CALLBACK(&OnDragLeaveThunk
), this);
1093 g_signal_connect(widget
, "button-press-event",
1094 G_CALLBACK(OnButtonPressedThunk
), this);
1095 g_signal_connect(widget
, "clicked",
1096 G_CALLBACK(OnFolderClickedThunk
), this);
1098 // Accept middle mouse clicking (which opens all). This must be called after
1099 // connecting to "button-press-event" because the handler it attaches stops
1100 // the propagation of that signal.
1101 gtk_util::SetButtonClickableByMouseButtons(widget
, true, true, false);
1104 const BookmarkNode
* BookmarkBarGtk::GetNodeForToolButton(GtkWidget
* widget
) {
1105 // First check to see if |button| is special cased.
1106 if (widget
== other_bookmarks_button_
)
1107 return model_
->other_node();
1108 else if (widget
== event_box_
.get() || widget
== overflow_button_
)
1109 return model_
->bookmark_bar_node();
1111 // Search the contents of |bookmark_toolbar_| for the corresponding widget
1112 // and find its index.
1113 GtkWidget
* item_to_find
= gtk_widget_get_parent(widget
);
1114 int index_to_use
= -1;
1116 GList
* children
= gtk_container_get_children(
1117 GTK_CONTAINER(bookmark_toolbar_
.get()));
1118 for (GList
* item
= children
; item
; item
= item
->next
, index
++) {
1119 if (item
->data
== item_to_find
) {
1120 index_to_use
= index
;
1124 g_list_free(children
);
1126 if (index_to_use
!= -1)
1127 return model_
->bookmark_bar_node()->GetChild(index_to_use
);
1132 void BookmarkBarGtk::PopupMenuForNode(GtkWidget
* sender
,
1133 const BookmarkNode
* node
,
1134 GdkEventButton
* event
) {
1135 if (!model_
->loaded()) {
1136 // Don't do anything if the model isn't loaded.
1140 const BookmarkNode
* parent
= NULL
;
1141 std::vector
<const BookmarkNode
*> nodes
;
1142 if (sender
== other_bookmarks_button_
) {
1143 nodes
.push_back(node
);
1144 parent
= model_
->bookmark_bar_node();
1145 } else if (sender
!= bookmark_toolbar_
.get()) {
1146 nodes
.push_back(node
);
1147 parent
= node
->parent();
1149 parent
= model_
->bookmark_bar_node();
1150 nodes
.push_back(parent
);
1153 GtkWindow
* window
= GTK_WINDOW(gtk_widget_get_toplevel(sender
));
1154 current_context_menu_controller_
.reset(
1155 new BookmarkContextMenuController(
1156 window
, this, browser_
, browser_
->profile(), page_navigator_
, parent
,
1158 current_context_menu_
.reset(
1159 new MenuGtk(NULL
, current_context_menu_controller_
->menu_model()));
1160 current_context_menu_
->PopupAsContext(
1161 gfx::Point(event
->x_root
, event
->y_root
),
1165 gboolean
BookmarkBarGtk::OnButtonPressed(GtkWidget
* sender
,
1166 GdkEventButton
* event
) {
1167 last_pressed_coordinates_
= gfx::Point(event
->x
, event
->y
);
1169 if (event
->button
== 3 && gtk_widget_get_visible(bookmark_hbox_
)) {
1170 const BookmarkNode
* node
= GetNodeForToolButton(sender
);
1172 DCHECK(page_navigator_
);
1173 PopupMenuForNode(sender
, node
, event
);
1179 void BookmarkBarGtk::OnClicked(GtkWidget
* sender
) {
1180 const BookmarkNode
* node
= GetNodeForToolButton(sender
);
1182 DCHECK(node
->is_url());
1183 DCHECK(page_navigator_
);
1185 RecordAppLaunch(browser_
->profile(), node
->url());
1186 chrome::OpenAll(window_
->GetNativeWindow(), page_navigator_
, node
,
1187 event_utils::DispositionForCurrentButtonPressEvent(),
1188 browser_
->profile());
1190 RecordBookmarkLaunch(node
, GetBookmarkLaunchLocation());
1193 void BookmarkBarGtk::OnButtonDragBegin(GtkWidget
* button
,
1194 GdkDragContext
* drag_context
) {
1195 GtkWidget
* button_parent
= gtk_widget_get_parent(button
);
1197 // The parent tool item might be removed during the drag. Ref it so |button|
1198 // won't get destroyed.
1199 g_object_ref(button_parent
);
1201 const BookmarkNode
* node
= GetNodeForToolButton(button
);
1202 DCHECK(!dragged_node_
);
1203 dragged_node_
= node
;
1204 DCHECK(dragged_node_
);
1206 drag_icon_
= GetDragRepresentationForNode(node
, model_
, theme_service_
);
1208 // We have to jump through some hoops to get the drag icon to line up because
1209 // it is a different size than the button.
1211 gtk_widget_size_request(drag_icon_
, &req
);
1212 gfx::Rect button_rect
= gtk_util::WidgetBounds(button
);
1213 gfx::Point drag_icon_relative
=
1214 gfx::Rect(req
.width
, req
.height
).CenterPoint() +
1215 (last_pressed_coordinates_
- button_rect
.CenterPoint());
1216 gtk_drag_set_icon_widget(drag_context
, drag_icon_
,
1217 drag_icon_relative
.x(),
1218 drag_icon_relative
.y());
1220 // Hide our node, but reserve space for it on the toolbar.
1221 int index
= gtk_toolbar_get_item_index(GTK_TOOLBAR(bookmark_toolbar_
.get()),
1222 GTK_TOOL_ITEM(button_parent
));
1223 gtk_widget_hide(button
);
1224 toolbar_drop_item_
= CreateBookmarkToolItem(dragged_node_
);
1225 g_object_ref_sink(GTK_OBJECT(toolbar_drop_item_
));
1226 gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bookmark_toolbar_
.get()),
1227 GTK_TOOL_ITEM(toolbar_drop_item_
), index
);
1228 // Make sure it stays hidden for the duration of the drag.
1229 gtk_widget_set_no_show_all(button
, TRUE
);
1232 void BookmarkBarGtk::OnButtonDragEnd(GtkWidget
* button
,
1233 GdkDragContext
* drag_context
) {
1234 gtk_widget_show(button
);
1235 gtk_widget_set_no_show_all(button
, FALSE
);
1237 ClearToolbarDropHighlighting();
1239 DCHECK(dragged_node_
);
1240 dragged_node_
= NULL
;
1243 gtk_widget_destroy(drag_icon_
);
1246 g_object_unref(gtk_widget_get_parent(button
));
1249 void BookmarkBarGtk::OnButtonDragGet(GtkWidget
* widget
,
1250 GdkDragContext
* context
,
1251 GtkSelectionData
* selection_data
,
1254 const BookmarkNode
* node
= BookmarkNodeForWidget(widget
);
1255 WriteBookmarkToSelection(
1256 node
, selection_data
, target_type
, browser_
->profile());
1259 void BookmarkBarGtk::OnAppsButtonClicked(GtkWidget
* sender
) {
1260 content::OpenURLParams
params(
1261 GURL(chrome::kChromeUIAppsURL
),
1262 content::Referrer(),
1263 event_utils::DispositionForCurrentButtonPressEvent(),
1264 content::PAGE_TRANSITION_AUTO_BOOKMARK
,
1266 browser_
->OpenURL(params
);
1267 RecordBookmarkAppsPageOpen(GetBookmarkLaunchLocation());
1270 void BookmarkBarGtk::OnFolderClicked(GtkWidget
* sender
) {
1271 // Stop its throbbing, if any.
1272 HoverControllerGtk
* hover_controller
=
1273 HoverControllerGtk::GetHoverControllerGtk(sender
);
1274 if (hover_controller
)
1275 hover_controller
->StartThrobbing(0);
1277 GdkEvent
* event
= gtk_get_current_event();
1278 if (event
->button
.button
== 1 ||
1279 (event
->button
.button
== 2 && sender
== overflow_button_
)) {
1280 RecordBookmarkFolderOpen(GetBookmarkLaunchLocation());
1281 PopupForButton(sender
);
1282 } else if (event
->button
.button
== 2) {
1283 const BookmarkNode
* node
= GetNodeForToolButton(sender
);
1284 chrome::OpenAll(window_
->GetNativeWindow(), page_navigator_
, node
,
1285 NEW_BACKGROUND_TAB
, browser_
->profile());
1287 gdk_event_free(event
);
1290 gboolean
BookmarkBarGtk::OnToolbarDragMotion(GtkWidget
* toolbar
,
1291 GdkDragContext
* context
,
1295 gint index
= gtk_toolbar_get_drop_index(GTK_TOOLBAR(toolbar
), x
, y
);
1296 return ItemDraggedOverToolbar(context
, index
, time
);
1299 void BookmarkBarGtk::OnToolbarSizeAllocate(GtkWidget
* widget
,
1300 GtkAllocation
* allocation
) {
1301 if (allocation
->width
== last_allocation_width_
) {
1302 // If the width hasn't changed, then the visibility of the chevron
1303 // doesn't need to change. This check prevents us from getting stuck in a
1304 // loop where allocates are queued indefinitely while the visibility of
1305 // overflow chevron toggles without actual resizes of the toolbar.
1308 last_allocation_width_
= allocation
->width
;
1313 void BookmarkBarGtk::OnDragReceived(GtkWidget
* widget
,
1314 GdkDragContext
* context
,
1316 GtkSelectionData
* selection_data
,
1317 guint target_type
, guint time
) {
1318 if (!edit_bookmarks_enabled_
.GetValue()) {
1319 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
1323 gboolean dnd_success
= FALSE
;
1324 gboolean delete_selection_data
= FALSE
;
1326 const BookmarkNode
* dest_node
= model_
->bookmark_bar_node();
1328 if (widget
== bookmark_toolbar_
.get()) {
1329 index
= gtk_toolbar_get_drop_index(
1330 GTK_TOOLBAR(bookmark_toolbar_
.get()), x
, y
);
1331 } else if (widget
== instructions_
) {
1332 dest_node
= model_
->bookmark_bar_node();
1335 index
= GetToolbarIndexForDragOverFolder(widget
, x
);
1337 dest_node
= GetNodeForToolButton(widget
);
1338 index
= dest_node
->child_count();
1342 switch (target_type
) {
1343 case ui::CHROME_BOOKMARK_ITEM
: {
1344 gint length
= gtk_selection_data_get_length(selection_data
);
1345 Pickle
pickle(reinterpret_cast<const char*>(
1346 gtk_selection_data_get_data(selection_data
)), length
);
1347 BookmarkNodeData drag_data
;
1348 if (drag_data
.ReadFromPickle(&pickle
)) {
1349 dnd_success
= chrome::DropBookmarks(browser_
->profile(),
1350 drag_data
, dest_node
, index
) != ui::DragDropTypes::DRAG_NONE
;
1355 case ui::CHROME_NAMED_URL
: {
1356 dnd_success
= CreateNewBookmarkFromNamedUrl(
1357 selection_data
, model_
, dest_node
, index
);
1361 case ui::TEXT_URI_LIST
: {
1362 dnd_success
= CreateNewBookmarksFromURIList(
1363 selection_data
, model_
, dest_node
, index
);
1367 case ui::NETSCAPE_URL
: {
1368 dnd_success
= CreateNewBookmarkFromNetscapeURL(
1369 selection_data
, model_
, dest_node
, index
);
1373 case ui::TEXT_PLAIN
: {
1374 guchar
* text
= gtk_selection_data_get_text(selection_data
);
1377 GURL
url(reinterpret_cast<char*>(text
));
1379 // TODO(estade): It would be nice to head this case off at drag motion,
1380 // so that it doesn't look like we can drag onto the bookmark bar.
1381 if (!url
.is_valid())
1383 base::string16 title
= GetNameForURL(url
);
1384 model_
->AddURL(dest_node
, index
, title
, url
);
1390 gtk_drag_finish(context
, dnd_success
, delete_selection_data
, time
);
1393 void BookmarkBarGtk::OnDragLeave(GtkWidget
* sender
,
1394 GdkDragContext
* context
,
1396 if (GTK_IS_BUTTON(sender
))
1397 gtk_drag_unhighlight(sender
);
1399 ClearToolbarDropHighlighting();
1402 gboolean
BookmarkBarGtk::OnFolderDragMotion(GtkWidget
* button
,
1403 GdkDragContext
* context
,
1407 if (!edit_bookmarks_enabled_
.GetValue())
1409 GdkAtom target_type
= gtk_drag_dest_find_target(button
, context
, NULL
);
1410 if (target_type
== GDK_NONE
)
1413 int index
= GetToolbarIndexForDragOverFolder(button
, x
);
1415 ClearToolbarDropHighlighting();
1417 // Drag is over middle of folder.
1418 gtk_drag_highlight(button
);
1419 if (target_type
== ui::GetAtomForTarget(ui::CHROME_BOOKMARK_ITEM
)) {
1420 gdk_drag_status(context
, GDK_ACTION_MOVE
, time
);
1422 gdk_drag_status(context
, GDK_ACTION_COPY
, time
);
1428 // Remove previous highlighting.
1429 gtk_drag_unhighlight(button
);
1430 return ItemDraggedOverToolbar(context
, index
, time
);
1433 gboolean
BookmarkBarGtk::OnEventBoxExpose(GtkWidget
* widget
,
1434 GdkEventExpose
* event
) {
1435 TRACE_EVENT0("ui::gtk", "BookmarkBarGtk::OnEventBoxExpose");
1436 GtkThemeService
* theme_provider
= theme_service_
;
1438 // We don't need to render the toolbar image in GTK mode, except when
1440 if (theme_provider
->UsingNativeTheme() &&
1441 bookmark_bar_state_
!= BookmarkBar::DETACHED
)
1444 if (bookmark_bar_state_
!= BookmarkBar::DETACHED
) {
1445 cairo_t
* cr
= gdk_cairo_create(gtk_widget_get_window(widget
));
1446 gdk_cairo_rectangle(cr
, &event
->area
);
1449 // Paint the background theme image.
1450 gfx::Point tabstrip_origin
=
1451 tabstrip_origin_provider_
->GetTabStripOriginForWidget(widget
);
1452 gtk_util::DrawThemedToolbarBackground(widget
, cr
, event
, tabstrip_origin
,
1457 gfx::Size web_contents_size
;
1458 if (!GetWebContentsSize(&web_contents_size
))
1460 gfx::CanvasSkiaPaint
canvas(event
, true);
1462 GtkAllocation allocation
;
1463 gtk_widget_get_allocation(widget
, &allocation
);
1465 gfx::Rect area
= gtk_widget_get_has_window(widget
) ?
1466 gfx::Rect(0, 0, allocation
.width
, allocation
.height
) :
1467 gfx::Rect(allocation
);
1468 NtpBackgroundUtil::PaintBackgroundDetachedMode(theme_provider
, &canvas
,
1469 area
, web_contents_size
.height());
1472 return FALSE
; // Propagate expose to children.
1475 void BookmarkBarGtk::OnEventBoxDestroy(GtkWidget
* widget
) {
1477 model_
->RemoveObserver(this);
1480 void BookmarkBarGtk::OnParentSizeAllocate(GtkWidget
* widget
,
1481 GtkAllocation
* allocation
) {
1482 // In detached mode, our layout depends on the size of the tab contents.
1483 // We get the size-allocate signal before the tab contents does, hence we
1484 // need to post a delayed task so we will paint correctly. Note that
1485 // gtk_widget_queue_draw by itself does not work, despite that it claims to
1487 if (bookmark_bar_state_
== BookmarkBar::DETACHED
) {
1488 base::MessageLoop::current()->PostTask(
1490 base::Bind(&BookmarkBarGtk::PaintEventBox
, weak_factory_
.GetWeakPtr()));
1494 void BookmarkBarGtk::OnThrobbingWidgetDestroy(GtkWidget
* widget
) {
1495 SetThrobbingWidget(NULL
);
1498 void BookmarkBarGtk::ShowImportDialog() {
1499 chrome::ShowImportDialog(browser_
);
1502 void BookmarkBarGtk::OnAppsPageShortcutVisibilityChanged() {
1503 const bool visible
=
1504 chrome::ShouldShowAppsShortcutInBookmarkBar(
1505 browser_
->profile(), browser_
->host_desktop_type());
1506 gtk_widget_set_visible(apps_shortcut_button_
, visible
);
1507 gtk_widget_set_no_show_all(apps_shortcut_button_
, !visible
);
1510 void BookmarkBarGtk::OnEditBookmarksEnabledChanged() {
1511 GtkDestDefaults dest_defaults
=
1512 *edit_bookmarks_enabled_
? GTK_DEST_DEFAULT_ALL
:
1513 GTK_DEST_DEFAULT_DROP
;
1514 gtk_drag_dest_set(overflow_button_
, dest_defaults
, NULL
, 0, kDragAction
);
1515 gtk_drag_dest_set(other_bookmarks_button_
, dest_defaults
,
1516 NULL
, 0, kDragAction
);
1517 ui::SetDestTargetList(overflow_button_
, kDestTargetList
);
1518 ui::SetDestTargetList(other_bookmarks_button_
, kDestTargetList
);