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