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