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