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