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