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