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