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.
5 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
12 #include "base/bind.h"
13 #include "base/debug/trace_event.h"
14 #include "base/i18n/rtl.h"
15 #include "base/metrics/histogram.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "chrome/browser/autocomplete/autocomplete_classifier.h"
19 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
20 #include "chrome/browser/autocomplete/autocomplete_match.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/themes/theme_properties.h"
24 #include "chrome/browser/themes/theme_service.h"
25 #include "chrome/browser/ui/browser.h"
26 #include "chrome/browser/ui/browser_navigator.h"
27 #include "chrome/browser/ui/browser_tabstrip.h"
28 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
29 #include "chrome/browser/ui/gtk/custom_button.h"
30 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
31 #include "chrome/browser/ui/gtk/gtk_util.h"
32 #include "chrome/browser/ui/gtk/tabs/dragged_tab_controller_gtk.h"
33 #include "chrome/browser/ui/gtk/tabs/tab_strip_menu_controller.h"
34 #include "chrome/browser/ui/tabs/tab_strip_model.h"
35 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
36 #include "content/public/browser/notification_source.h"
37 #include "content/public/browser/user_metrics.h"
38 #include "content/public/browser/web_contents.h"
39 #include "grit/generated_resources.h"
40 #include "grit/theme_resources.h"
41 #include "grit/ui_resources.h"
42 #include "ui/base/dragdrop/gtk_dnd_util.h"
43 #include "ui/base/gtk/gtk_screen_util.h"
44 #include "ui/base/l10n/l10n_util.h"
45 #include "ui/base/resource/resource_bundle.h"
46 #include "ui/gfx/animation/animation_delegate.h"
47 #include "ui/gfx/animation/slide_animation.h"
48 #include "ui/gfx/gtk_compat.h"
49 #include "ui/gfx/gtk_util.h"
50 #include "ui/gfx/image/image.h"
51 #include "ui/gfx/point.h"
53 using base::UserMetricsAction
;
54 using content::WebContents
;
58 const int kDefaultAnimationDurationMs
= 100;
59 const int kResizeLayoutAnimationDurationMs
= 166;
60 const int kReorderAnimationDurationMs
= 166;
61 const int kMiniTabAnimationDurationMs
= 150;
63 const int kNewTabButtonHOffset
= -5;
64 const int kNewTabButtonVOffset
= 5;
66 // The delay between when the mouse leaves the tabstrip and the resize animation
68 const int kResizeTabsTimeMs
= 300;
70 // A very short time just to make sure we don't clump up our Layout() calls
71 // during slow window resizes.
72 const int kLayoutAfterSizeAllocateMs
= 10;
74 // The range outside of the tabstrip where the pointer must enter/leave to
75 // start/stop the resize animation.
76 const int kTabStripAnimationVSlop
= 40;
78 // The horizontal offset from one tab to the next, which results in overlapping
80 const int kTabHOffset
= -16;
82 // Inverse ratio of the width of a tab edge to the width of the tab. When
83 // hovering over the left or right edge of a tab, the drop indicator will
84 // point between tabs.
85 const int kTabEdgeRatioInverse
= 4;
87 // Size of the drop indicator.
88 static int drop_indicator_width
;
89 static int drop_indicator_height
;
91 inline int Round(double x
) {
92 return static_cast<int>(x
+ 0.5);
95 // widget->allocation is not guaranteed to be set. After window creation,
96 // we pick up the normal bounds by connecting to the configure-event signal.
97 gfx::Rect
GetInitialWidgetBounds(GtkWidget
* widget
) {
98 GtkRequisition request
;
99 gtk_widget_size_request(widget
, &request
);
100 return gfx::Rect(0, 0, request
.width
, request
.height
);
103 // Sort rectangles based on their x position. We don't care about y position
104 // so we don't bother breaking ties.
105 int CompareGdkRectangles(const void* p1
, const void* p2
) {
106 int p1_x
= static_cast<const GdkRectangle
*>(p1
)->x
;
107 int p2_x
= static_cast<const GdkRectangle
*>(p2
)->x
;
110 else if (p1_x
== p2_x
)
115 bool GdkRectMatchesTabFaviconBounds(const GdkRectangle
& gdk_rect
, TabGtk
* tab
) {
116 gfx::Rect favicon_bounds
= tab
->favicon_bounds();
117 return gdk_rect
.x
== favicon_bounds
.x() + tab
->x() &&
118 gdk_rect
.y
== favicon_bounds
.y() + tab
->y() &&
119 gdk_rect
.width
== favicon_bounds
.width() &&
120 gdk_rect
.height
== favicon_bounds
.height();
125 ////////////////////////////////////////////////////////////////////////////////
129 // A base class for all TabStrip animations.
131 class TabStripGtk::TabAnimation
: public gfx::AnimationDelegate
{
133 friend class TabStripGtk
;
135 // Possible types of animation.
145 TabAnimation(TabStripGtk
* tabstrip
, Type type
)
146 : tabstrip_(tabstrip
),
148 start_selected_width_(0),
149 start_unselected_width_(0),
150 end_selected_width_(0),
151 end_unselected_width_(0),
152 layout_on_completion_(false),
155 virtual ~TabAnimation() {}
157 Type
type() const { return type_
; }
160 animation_
.SetSlideDuration(GetDuration());
161 animation_
.SetTweenType(gfx::Tween::EASE_OUT
);
162 if (!animation_
.IsShowing()) {
172 void set_layout_on_completion(bool layout_on_completion
) {
173 layout_on_completion_
= layout_on_completion
;
176 // Retrieves the width for the Tab at the specified index if an animation is
178 static double GetCurrentTabWidth(TabStripGtk
* tabstrip
,
179 TabStripGtk::TabAnimation
* animation
,
181 TabGtk
* tab
= tabstrip
->GetTabAt(index
);
184 tab_width
= TabGtk::GetMiniWidth();
186 double unselected
, selected
;
187 tabstrip
->GetCurrentTabWidths(&unselected
, &selected
);
188 tab_width
= tab
->IsActive() ? selected
: unselected
;
192 double specified_tab_width
= animation
->GetWidthForTab(index
);
193 if (specified_tab_width
!= -1)
194 tab_width
= specified_tab_width
;
200 // Overridden from gfx::AnimationDelegate:
201 virtual void AnimationProgressed(const gfx::Animation
* animation
) OVERRIDE
{
202 tabstrip_
->AnimationLayout(end_unselected_width_
);
205 virtual void AnimationEnded(const gfx::Animation
* animation
) OVERRIDE
{
206 tabstrip_
->FinishAnimation(this, layout_on_completion_
);
207 // This object is destroyed now, so we can't do anything else after this.
210 virtual void AnimationCanceled(const gfx::Animation
* animation
) OVERRIDE
{
211 AnimationEnded(animation
);
214 // Returns the gap before the tab at the specified index. Subclass if during
215 // an animation you need to insert a gap before a tab.
216 virtual double GetGapWidth(int index
) {
221 // Returns the duration of the animation.
222 virtual int GetDuration() const {
223 return kDefaultAnimationDurationMs
;
226 // Subclasses override to return the width of the Tab at the specified index
227 // at the current animation frame. -1 indicates the default width should be
229 virtual double GetWidthForTab(int index
) const {
230 return -1; // Use default.
233 // Figure out the desired start and end widths for the specified pre- and
234 // post- animation tab counts.
235 void GenerateStartAndEndWidths(int start_tab_count
, int end_tab_count
,
236 int start_mini_count
,
237 int end_mini_count
) {
238 tabstrip_
->GetDesiredTabWidths(start_tab_count
, start_mini_count
,
239 &start_unselected_width_
,
240 &start_selected_width_
);
241 double standard_tab_width
=
242 static_cast<double>(TabRendererGtk::GetStandardSize().width());
244 if ((end_tab_count
- start_tab_count
) > 0 &&
245 start_unselected_width_
< standard_tab_width
) {
246 double minimum_tab_width
= static_cast<double>(
247 TabRendererGtk::GetMinimumUnselectedSize().width());
248 start_unselected_width_
-= minimum_tab_width
/ start_tab_count
;
251 tabstrip_
->GenerateIdealBounds();
252 tabstrip_
->GetDesiredTabWidths(end_tab_count
, end_mini_count
,
253 &end_unselected_width_
,
254 &end_selected_width_
);
257 TabStripGtk
* tabstrip_
;
258 gfx::SlideAnimation animation_
;
260 double start_selected_width_
;
261 double start_unselected_width_
;
262 double end_selected_width_
;
263 double end_unselected_width_
;
266 // True if a complete re-layout is required upon completion of the animation.
267 // Subclasses set this if they don't perform a complete layout
268 // themselves and canceling the animation may leave the strip in an
269 // inconsistent state.
270 bool layout_on_completion_
;
274 DISALLOW_COPY_AND_ASSIGN(TabAnimation
);
277 ////////////////////////////////////////////////////////////////////////////////
279 // Handles insertion of a Tab at |index|.
280 class InsertTabAnimation
: public TabStripGtk::TabAnimation
{
282 InsertTabAnimation(TabStripGtk
* tabstrip
, int index
)
283 : TabAnimation(tabstrip
, INSERT
),
285 int tab_count
= tabstrip
->GetTabCount();
286 int end_mini_count
= tabstrip
->GetMiniTabCount();
287 int start_mini_count
= end_mini_count
;
288 if (index
< end_mini_count
)
290 GenerateStartAndEndWidths(tab_count
- 1, tab_count
, start_mini_count
,
293 virtual ~InsertTabAnimation() {}
296 // Overridden from TabStripGtk::TabAnimation:
297 virtual double GetWidthForTab(int index
) const OVERRIDE
{
298 if (index
== index_
) {
299 bool is_selected
= tabstrip_
->model()->active_index() == index
;
300 double start_width
, target_width
;
301 if (index
< tabstrip_
->GetMiniTabCount()) {
302 start_width
= TabGtk::GetMinimumSelectedSize().width();
303 target_width
= TabGtk::GetMiniWidth();
306 is_selected
? end_unselected_width_
: end_selected_width_
;
308 is_selected
? TabGtk::GetMinimumSelectedSize().width() :
309 TabGtk::GetMinimumUnselectedSize().width();
312 double delta
= target_width
- start_width
;
314 return start_width
+ (delta
* animation_
.GetCurrentValue());
319 if (tabstrip_
->GetTabAt(index
)->mini())
320 return TabGtk::GetMiniWidth();
322 if (tabstrip_
->GetTabAt(index
)->IsActive()) {
323 double delta
= end_selected_width_
- start_selected_width_
;
324 return start_selected_width_
+ (delta
* animation_
.GetCurrentValue());
327 double delta
= end_unselected_width_
- start_unselected_width_
;
328 return start_unselected_width_
+ (delta
* animation_
.GetCurrentValue());
334 DISALLOW_COPY_AND_ASSIGN(InsertTabAnimation
);
337 ////////////////////////////////////////////////////////////////////////////////
339 // Handles removal of a Tab from |index|
340 class RemoveTabAnimation
: public TabStripGtk::TabAnimation
{
342 RemoveTabAnimation(TabStripGtk
* tabstrip
, int index
, WebContents
* contents
)
343 : TabAnimation(tabstrip
, REMOVE
),
345 int tab_count
= tabstrip
->GetTabCount();
346 int start_mini_count
= tabstrip
->GetMiniTabCount();
347 int end_mini_count
= start_mini_count
;
348 if (index
< start_mini_count
)
350 GenerateStartAndEndWidths(tab_count
, tab_count
- 1, start_mini_count
,
352 // If the last non-mini-tab is being removed we force a layout on
353 // completion. This is necessary as the value returned by GetTabHOffset
354 // changes once the tab is actually removed (which happens at the end of
355 // the animation), and unless we layout GetTabHOffset won't be called after
357 // We do the same when the last mini-tab is being removed for the same
359 set_layout_on_completion(start_mini_count
> 0 &&
360 (end_mini_count
== 0 ||
361 (start_mini_count
== end_mini_count
&&
362 tab_count
== start_mini_count
+ 1)));
365 virtual ~RemoveTabAnimation() {}
367 // Returns the index of the tab being removed.
368 int index() const { return index_
; }
371 // Overridden from TabStripGtk::TabAnimation:
372 virtual double GetWidthForTab(int index
) const OVERRIDE
{
373 TabGtk
* tab
= tabstrip_
->GetTabAt(index
);
375 if (index
== index_
) {
376 // The tab(s) being removed are gradually shrunken depending on the state
379 return animation_
.CurrentValueBetween(TabGtk::GetMiniWidth(),
383 // Removed animated Tabs are never selected.
384 double start_width
= start_unselected_width_
;
385 // Make sure target_width is at least abs(kTabHOffset), otherwise if
386 // less than kTabHOffset during layout tabs get negatively offset.
387 double target_width
=
388 std::max(abs(kTabHOffset
),
389 TabGtk::GetMinimumUnselectedSize().width() + kTabHOffset
);
390 return animation_
.CurrentValueBetween(start_width
, target_width
);
394 return TabGtk::GetMiniWidth();
396 if (tabstrip_
->available_width_for_tabs_
!= -1 &&
397 index_
!= tabstrip_
->GetTabCount() - 1) {
398 return TabStripGtk::TabAnimation::GetWidthForTab(index
);
401 // All other tabs are sized according to the start/end widths specified at
402 // the start of the animation.
403 if (tab
->IsActive()) {
404 double delta
= end_selected_width_
- start_selected_width_
;
405 return start_selected_width_
+ (delta
* animation_
.GetCurrentValue());
408 double delta
= end_unselected_width_
- start_unselected_width_
;
409 return start_unselected_width_
+ (delta
* animation_
.GetCurrentValue());
412 virtual void AnimationEnded(const gfx::Animation
* animation
) OVERRIDE
{
413 tabstrip_
->RemoveTabAt(index_
);
414 TabStripGtk::TabAnimation::AnimationEnded(animation
);
420 DISALLOW_COPY_AND_ASSIGN(RemoveTabAnimation
);
423 ////////////////////////////////////////////////////////////////////////////////
425 // Handles the movement of a Tab from one position to another.
426 class MoveTabAnimation
: public TabStripGtk::TabAnimation
{
428 MoveTabAnimation(TabStripGtk
* tabstrip
, int tab_a_index
, int tab_b_index
)
429 : TabAnimation(tabstrip
, MOVE
),
430 start_tab_a_bounds_(tabstrip_
->GetIdealBounds(tab_b_index
)),
431 start_tab_b_bounds_(tabstrip_
->GetIdealBounds(tab_a_index
)) {
432 tab_a_
= tabstrip_
->GetTabAt(tab_a_index
);
433 tab_b_
= tabstrip_
->GetTabAt(tab_b_index
);
435 // Since we don't do a full TabStrip re-layout, we need to force a full
436 // layout upon completion since we're not guaranteed to be in a good state
437 // if for example the animation is canceled.
438 set_layout_on_completion(true);
440 virtual ~MoveTabAnimation() {}
442 // Overridden from gfx::AnimationDelegate:
443 virtual void AnimationProgressed(const gfx::Animation
* animation
) OVERRIDE
{
445 double distance
= start_tab_b_bounds_
.x() - start_tab_a_bounds_
.x();
446 double delta
= distance
* animation_
.GetCurrentValue();
447 double new_x
= start_tab_a_bounds_
.x() + delta
;
448 gfx::Rect
bounds(Round(new_x
), start_tab_a_bounds_
.y(), tab_a_
->width(),
450 tabstrip_
->SetTabBounds(tab_a_
, bounds
);
453 distance
= start_tab_a_bounds_
.x() - start_tab_b_bounds_
.x();
454 delta
= distance
* animation_
.GetCurrentValue();
455 new_x
= start_tab_b_bounds_
.x() + delta
;
456 bounds
= gfx::Rect(Round(new_x
), start_tab_b_bounds_
.y(), tab_b_
->width(),
458 tabstrip_
->SetTabBounds(tab_b_
, bounds
);
462 // Overridden from TabStripGtk::TabAnimation:
463 virtual int GetDuration() const OVERRIDE
{
464 return kReorderAnimationDurationMs
;
468 // The two tabs being exchanged.
472 // ...and their bounds.
473 gfx::Rect start_tab_a_bounds_
;
474 gfx::Rect start_tab_b_bounds_
;
476 DISALLOW_COPY_AND_ASSIGN(MoveTabAnimation
);
479 ////////////////////////////////////////////////////////////////////////////////
481 // Handles the animated resize layout of the entire TabStrip from one width
483 class ResizeLayoutAnimation
: public TabStripGtk::TabAnimation
{
485 explicit ResizeLayoutAnimation(TabStripGtk
* tabstrip
)
486 : TabAnimation(tabstrip
, RESIZE
) {
487 int tab_count
= tabstrip
->GetTabCount();
488 int mini_tab_count
= tabstrip
->GetMiniTabCount();
489 GenerateStartAndEndWidths(tab_count
, tab_count
, mini_tab_count
,
493 virtual ~ResizeLayoutAnimation() {}
495 // Overridden from gfx::AnimationDelegate:
496 virtual void AnimationEnded(const gfx::Animation
* animation
) OVERRIDE
{
497 tabstrip_
->needs_resize_layout_
= false;
498 TabStripGtk::TabAnimation::AnimationEnded(animation
);
502 // Overridden from TabStripGtk::TabAnimation:
503 virtual int GetDuration() const OVERRIDE
{
504 return kResizeLayoutAnimationDurationMs
;
507 virtual double GetWidthForTab(int index
) const OVERRIDE
{
508 TabGtk
* tab
= tabstrip_
->GetTabAt(index
);
511 return TabGtk::GetMiniWidth();
513 if (tab
->IsActive()) {
514 return animation_
.CurrentValueBetween(start_selected_width_
,
515 end_selected_width_
);
518 return animation_
.CurrentValueBetween(start_unselected_width_
,
519 end_unselected_width_
);
523 // We need to start from the current widths of the Tabs as they were last
524 // laid out, _not_ the last known good state, which is what'll be done if we
525 // don't measure the Tab sizes here and just go with the default TabAnimation
527 void InitStartState() {
528 for (int i
= 0; i
< tabstrip_
->GetTabCount(); ++i
) {
529 TabGtk
* current_tab
= tabstrip_
->GetTabAt(i
);
530 if (!current_tab
->mini()) {
531 if (current_tab
->IsActive()) {
532 start_selected_width_
= current_tab
->width();
534 start_unselected_width_
= current_tab
->width();
540 DISALLOW_COPY_AND_ASSIGN(ResizeLayoutAnimation
);
543 // Handles a tabs mini-state changing while the tab does not change position
545 class MiniTabAnimation
: public TabStripGtk::TabAnimation
{
547 MiniTabAnimation(TabStripGtk
* tabstrip
, int index
)
548 : TabAnimation(tabstrip
, MINI
),
550 int tab_count
= tabstrip
->GetTabCount();
551 int start_mini_count
= tabstrip
->GetMiniTabCount();
552 int end_mini_count
= start_mini_count
;
553 if (tabstrip
->GetTabAt(index
)->mini())
557 tabstrip_
->GetTabAt(index
)->set_animating_mini_change(true);
558 GenerateStartAndEndWidths(tab_count
, tab_count
, start_mini_count
,
563 // Overridden from TabStripGtk::TabAnimation:
564 virtual int GetDuration() const OVERRIDE
{
565 return kMiniTabAnimationDurationMs
;
568 virtual double GetWidthForTab(int index
) const OVERRIDE
{
569 TabGtk
* tab
= tabstrip_
->GetTabAt(index
);
571 if (index
== index_
) {
573 return animation_
.CurrentValueBetween(
574 start_selected_width_
,
575 static_cast<double>(TabGtk::GetMiniWidth()));
577 return animation_
.CurrentValueBetween(
578 static_cast<double>(TabGtk::GetMiniWidth()),
579 end_selected_width_
);
581 } else if (tab
->mini()) {
582 return TabGtk::GetMiniWidth();
585 if (tab
->IsActive()) {
586 return animation_
.CurrentValueBetween(start_selected_width_
,
587 end_selected_width_
);
590 return animation_
.CurrentValueBetween(start_unselected_width_
,
591 end_unselected_width_
);
595 // Index of the tab whose mini-state changed.
598 DISALLOW_COPY_AND_ASSIGN(MiniTabAnimation
);
601 ////////////////////////////////////////////////////////////////////////////////
603 // Handles the animation when a tabs mini-state changes and the tab moves as a
605 class MiniMoveAnimation
: public TabStripGtk::TabAnimation
{
607 MiniMoveAnimation(TabStripGtk
* tabstrip
,
610 const gfx::Rect
& start_bounds
)
611 : TabAnimation(tabstrip
, MINI_MOVE
),
612 tab_(tabstrip
->GetTabAt(to_index
)),
613 start_bounds_(start_bounds
),
614 from_index_(from_index
),
615 to_index_(to_index
) {
616 int tab_count
= tabstrip
->GetTabCount();
617 int start_mini_count
= tabstrip
->GetMiniTabCount();
618 int end_mini_count
= start_mini_count
;
619 if (tabstrip
->GetTabAt(to_index
)->mini())
623 GenerateStartAndEndWidths(tab_count
, tab_count
, start_mini_count
,
625 target_bounds_
= tabstrip
->GetIdealBounds(to_index
);
626 tab_
->set_animating_mini_change(true);
629 // Overridden from gfx::AnimationDelegate:
630 virtual void AnimationProgressed(const gfx::Animation
* animation
) OVERRIDE
{
631 // Do the normal layout.
632 TabAnimation::AnimationProgressed(animation
);
634 // Then special case the position of the tab being moved.
635 int x
= animation_
.CurrentValueBetween(start_bounds_
.x(),
637 int width
= animation_
.CurrentValueBetween(start_bounds_
.width(),
638 target_bounds_
.width());
639 gfx::Rect
tab_bounds(x
, start_bounds_
.y(), width
,
640 start_bounds_
.height());
641 tabstrip_
->SetTabBounds(tab_
, tab_bounds
);
644 virtual void AnimationEnded(const gfx::Animation
* animation
) OVERRIDE
{
645 tabstrip_
->needs_resize_layout_
= false;
646 TabStripGtk::TabAnimation::AnimationEnded(animation
);
649 virtual double GetGapWidth(int index
) OVERRIDE
{
650 if (to_index_
< from_index_
) {
651 // The tab was made mini.
652 if (index
== to_index_
) {
653 double current_size
=
654 animation_
.CurrentValueBetween(0, target_bounds_
.width());
655 if (current_size
< -kTabHOffset
)
656 return -(current_size
+ kTabHOffset
);
657 } else if (index
== from_index_
+ 1) {
658 return animation_
.CurrentValueBetween(start_bounds_
.width(), 0);
661 // The tab was was made a normal tab.
662 if (index
== from_index_
) {
663 return animation_
.CurrentValueBetween(
664 TabGtk::GetMiniWidth() + kTabHOffset
, 0);
671 // Overridden from TabStripGtk::TabAnimation:
672 virtual int GetDuration() const OVERRIDE
{
673 return kReorderAnimationDurationMs
;
676 virtual double GetWidthForTab(int index
) const OVERRIDE
{
677 TabGtk
* tab
= tabstrip_
->GetTabAt(index
);
679 if (index
== to_index_
)
680 return animation_
.CurrentValueBetween(0, target_bounds_
.width());
683 return TabGtk::GetMiniWidth();
685 if (tab
->IsActive()) {
686 return animation_
.CurrentValueBetween(start_selected_width_
,
687 end_selected_width_
);
690 return animation_
.CurrentValueBetween(start_unselected_width_
,
691 end_unselected_width_
);
695 // The tab being moved.
698 // Initial bounds of tab_.
699 gfx::Rect start_bounds_
;
702 gfx::Rect target_bounds_
;
704 // Start and end indices of the tab.
708 DISALLOW_COPY_AND_ASSIGN(MiniMoveAnimation
);
711 ////////////////////////////////////////////////////////////////////////////////
712 // TabStripGtk, public:
715 const int TabStripGtk::mini_to_non_mini_gap_
= 3;
717 TabStripGtk::TabStripGtk(TabStripModel
* model
, BrowserWindowGtk
* window
)
718 : current_unselected_width_(TabGtk::GetStandardSize().width()),
719 current_selected_width_(TabGtk::GetStandardSize().width()),
720 available_width_for_tabs_(-1),
721 needs_resize_layout_(false),
722 tab_vertical_offset_(0),
725 theme_service_(GtkThemeService::GetFrom(model
->profile())),
726 added_as_message_loop_observer_(false),
727 hover_tab_selector_(model
),
729 layout_factory_(this) {
732 TabStripGtk::~TabStripGtk() {
733 model_
->RemoveObserver(this);
736 // Free any remaining tabs. This is needed to free the very last tab,
737 // because it is not animated on close. This also happens when all of the
738 // tabs are closed at once.
739 std::vector
<TabData
>::iterator iterator
= tab_data_
.begin();
740 for (; iterator
< tab_data_
.end(); iterator
++) {
741 delete iterator
->tab
;
746 // Make sure we unhook ourselves as a message loop observer so that we don't
747 // crash in the case where the user closes the last tab in a window.
748 RemoveMessageLoopObserver();
751 void TabStripGtk::Init() {
752 model_
->AddObserver(this);
754 tabstrip_
.Own(gtk_fixed_new());
755 ViewIDUtil::SetID(tabstrip_
.get(), VIEW_ID_TAB_STRIP
);
756 // We want the tab strip to be horizontally shrinkable, so that the Chrome
757 // window can be resized freely.
758 gtk_widget_set_size_request(tabstrip_
.get(), 0,
759 TabGtk::GetMinimumUnselectedSize().height());
760 gtk_widget_set_app_paintable(tabstrip_
.get(), TRUE
);
761 gtk_drag_dest_set(tabstrip_
.get(), GTK_DEST_DEFAULT_ALL
,
763 static_cast<GdkDragAction
>(
764 GDK_ACTION_COPY
| GDK_ACTION_MOVE
| GDK_ACTION_LINK
));
765 static const int targets
[] = { ui::TEXT_URI_LIST
,
769 ui::SetDestTargetList(tabstrip_
.get(), targets
);
771 g_signal_connect(tabstrip_
.get(), "map",
772 G_CALLBACK(OnMapThunk
), this);
773 g_signal_connect(tabstrip_
.get(), "expose-event",
774 G_CALLBACK(OnExposeThunk
), this);
775 g_signal_connect(tabstrip_
.get(), "size-allocate",
776 G_CALLBACK(OnSizeAllocateThunk
), this);
777 g_signal_connect(tabstrip_
.get(), "drag-motion",
778 G_CALLBACK(OnDragMotionThunk
), this);
779 g_signal_connect(tabstrip_
.get(), "drag-drop",
780 G_CALLBACK(OnDragDropThunk
), this);
781 g_signal_connect(tabstrip_
.get(), "drag-leave",
782 G_CALLBACK(OnDragLeaveThunk
), this);
783 g_signal_connect(tabstrip_
.get(), "drag-data-received",
784 G_CALLBACK(OnDragDataReceivedThunk
), this);
786 newtab_button_
.reset(MakeNewTabButton());
787 newtab_surface_bounds_
.SetRect(0, 0, newtab_button_
->SurfaceWidth(),
788 newtab_button_
->SurfaceHeight());
790 gtk_widget_show_all(tabstrip_
.get());
792 bounds_
= GetInitialWidgetBounds(tabstrip_
.get());
794 if (drop_indicator_width
== 0) {
795 // Direction doesn't matter, both images are the same size.
796 GdkPixbuf
* drop_image
= GetDropArrowImage(true)->ToGdkPixbuf();
797 drop_indicator_width
= gdk_pixbuf_get_width(drop_image
);
798 drop_indicator_height
= gdk_pixbuf_get_height(drop_image
);
801 registrar_
.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED
,
802 content::Source
<ThemeService
>(theme_service_
));
803 theme_service_
->InitThemesFor(this);
805 ViewIDUtil::SetDelegateForWidget(widget(), this);
808 void TabStripGtk::Show() {
809 gtk_widget_show(tabstrip_
.get());
812 void TabStripGtk::Hide() {
813 gtk_widget_hide(tabstrip_
.get());
816 bool TabStripGtk::IsActiveDropTarget() const {
817 for (int i
= 0; i
< GetTabCount(); ++i
) {
818 TabGtk
* tab
= GetTabAt(i
);
825 void TabStripGtk::Layout() {
828 // - animation completion
831 GenerateIdealBounds();
832 int tab_count
= GetTabCount();
834 for (int i
= 0; i
< tab_count
; ++i
) {
835 const gfx::Rect
& bounds
= tab_data_
.at(i
).ideal_bounds
;
836 TabGtk
* tab
= GetTabAt(i
);
837 tab
->set_animating_mini_change(false);
838 tab
->set_vertical_offset(tab_vertical_offset_
);
839 SetTabBounds(tab
, bounds
);
840 tab_right
= bounds
.right();
841 tab_right
+= GetTabHOffset(i
+ 1);
844 LayoutNewTabButton(static_cast<double>(tab_right
), current_unselected_width_
);
847 void TabStripGtk::SchedulePaint() {
848 gtk_widget_queue_draw(tabstrip_
.get());
851 void TabStripGtk::SetBounds(const gfx::Rect
& bounds
) {
855 void TabStripGtk::UpdateLoadingAnimations() {
856 for (int i
= 0, index
= 0; i
< GetTabCount(); ++i
, ++index
) {
857 TabGtk
* current_tab
= GetTabAt(i
);
858 if (current_tab
->closing()) {
861 TabRendererGtk::AnimationState state
;
862 content::WebContents
* web_contents
= model_
->GetWebContentsAt(index
);
863 if (!web_contents
|| !web_contents
->IsLoading()) {
864 state
= TabGtk::ANIMATION_NONE
;
865 } else if (web_contents
->IsWaitingForResponse()) {
866 state
= TabGtk::ANIMATION_WAITING
;
868 state
= TabGtk::ANIMATION_LOADING
;
870 if (current_tab
->ValidateLoadingAnimation(state
)) {
871 // Queue the tab's icon area to be repainted.
872 gfx::Rect favicon_bounds
= current_tab
->favicon_bounds();
873 gtk_widget_queue_draw_area(tabstrip_
.get(),
874 favicon_bounds
.x() + current_tab
->x(),
875 favicon_bounds
.y() + current_tab
->y(),
876 favicon_bounds
.width(),
877 favicon_bounds
.height());
883 bool TabStripGtk::IsCompatibleWith(TabStripGtk
* other
) {
884 return model_
->profile() == other
->model()->profile();
887 bool TabStripGtk::IsAnimating() const {
888 return active_animation_
.get() != NULL
;
891 void TabStripGtk::DestroyDragController() {
892 drag_controller_
.reset();
895 void TabStripGtk::DestroyDraggedTab(TabGtk
* tab
) {
896 // We could be running an animation that references this Tab.
899 // Make sure we leave the tab_data_ vector in a consistent state, otherwise
900 // we'll be pointing to tabs that have been deleted and removed from the
902 std::vector
<TabData
>::iterator it
= tab_data_
.begin();
903 for (; it
!= tab_data_
.end(); ++it
) {
904 if (it
->tab
== tab
) {
905 if (!model_
->closing_all())
906 NOTREACHED() << "Leaving in an inconsistent state!";
912 gtk_container_remove(GTK_CONTAINER(tabstrip_
.get()), tab
->widget());
913 // If we delete the dragged source tab here, the DestroyDragWidget posted
914 // task will be run after the tab is deleted, leading to a crash.
915 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, tab
);
917 // Force a layout here, because if we've just quickly drag detached a Tab,
918 // the stopping of the active animation above may have left the TabStrip in a
919 // bad (visual) state.
923 gfx::Rect
TabStripGtk::GetIdealBounds(int index
) {
924 DCHECK(index
>= 0 && index
< GetTabCount());
925 return tab_data_
.at(index
).ideal_bounds
;
928 void TabStripGtk::SetVerticalOffset(int offset
) {
929 tab_vertical_offset_
= offset
;
933 gfx::Point
TabStripGtk::GetTabStripOriginForWidget(GtkWidget
* target
) {
935 GtkAllocation widget_allocation
;
936 gtk_widget_get_allocation(widget(), &widget_allocation
);
937 if (!gtk_widget_translate_coordinates(widget(), target
,
938 -widget_allocation
.x
, 0, &x
, &y
)) {
939 // If the tab strip isn't showing, give the coordinates relative to the
941 if (!gtk_widget_translate_coordinates(
942 gtk_widget_get_toplevel(widget()), target
, 0, 0, &x
, &y
)) {
946 if (!gtk_widget_get_has_window(target
)) {
947 GtkAllocation target_allocation
;
948 gtk_widget_get_allocation(target
, &target_allocation
);
949 x
+= target_allocation
.x
;
950 y
+= target_allocation
.y
;
952 return gfx::Point(x
, y
);
955 ////////////////////////////////////////////////////////////////////////////////
956 // ViewIDUtil::Delegate implementation
958 GtkWidget
* TabStripGtk::GetWidgetForViewID(ViewID view_id
) {
959 if (GetTabCount() > 0) {
960 if (view_id
== VIEW_ID_TAB_LAST
) {
961 return GetTabAt(GetTabCount() - 1)->widget();
962 } else if ((view_id
>= VIEW_ID_TAB_0
) && (view_id
< VIEW_ID_TAB_LAST
)) {
963 int index
= view_id
- VIEW_ID_TAB_0
;
964 if (index
>= 0 && index
< GetTabCount()) {
965 return GetTabAt(index
)->widget();
975 ////////////////////////////////////////////////////////////////////////////////
976 // TabStripGtk, TabStripModelObserver implementation:
978 void TabStripGtk::TabInsertedAt(WebContents
* contents
,
981 TRACE_EVENT0("ui::gtk", "TabStripGtk::TabInsertedAt");
984 DCHECK(index
== TabStripModel::kNoTab
|| model_
->ContainsIndex(index
));
988 bool contains_tab
= false;
990 // First see if this Tab is one that was dragged out of this TabStrip and is
991 // now being dragged back in. In this case, the DraggedTabController actually
992 // has the Tab already constructed and we can just insert it into our list
994 if (IsDragSessionActive()) {
995 tab
= drag_controller_
->GetDraggedTabForContents(contents
);
997 // If the Tab was detached, it would have been animated closed but not
998 // removed, so we need to reset this property.
999 tab
->set_closing(false);
1000 tab
->ValidateLoadingAnimation(TabRendererGtk::ANIMATION_NONE
);
1001 tab
->SetVisible(true);
1004 // See if we're already in the list. We don't want to add ourselves twice.
1005 std::vector
<TabData
>::const_iterator iter
= tab_data_
.begin();
1006 for (; iter
!= tab_data_
.end() && !contains_tab
; ++iter
) {
1007 if (iter
->tab
== tab
)
1008 contains_tab
= true;
1013 tab
= new TabGtk(this);
1015 // Only insert if we're not already in the list.
1016 if (!contains_tab
) {
1017 TabData d
= { tab
, gfx::Rect() };
1018 tab_data_
.insert(tab_data_
.begin() + index
, d
);
1019 tab
->UpdateData(contents
, model_
->IsAppTab(index
), false);
1021 tab
->set_mini(model_
->IsMiniTab(index
));
1022 tab
->set_app(model_
->IsAppTab(index
));
1023 tab
->SetBlocked(model_
->IsTabBlocked(index
));
1025 if (gtk_widget_get_parent(tab
->widget()) != tabstrip_
.get())
1026 gtk_fixed_put(GTK_FIXED(tabstrip_
.get()), tab
->widget(), 0, 0);
1028 // Don't animate the first tab; it looks weird.
1029 if (GetTabCount() > 1) {
1030 StartInsertTabAnimation(index
);
1031 // We added the tab at 0x0, we need to force an animation step otherwise
1032 // if GTK paints before the animation event the tab is painted at 0x0
1033 // which is most likely not where it should be positioned.
1034 active_animation_
->AnimationProgressed(NULL
);
1042 void TabStripGtk::TabDetachedAt(WebContents
* contents
, int index
) {
1043 GenerateIdealBounds();
1044 StartRemoveTabAnimation(index
, contents
);
1045 // Have to do this _after_ calling StartRemoveTabAnimation, so that any
1046 // previous remove is completed fully and index is valid in sync with the
1048 GetTabAt(index
)->set_closing(true);
1051 void TabStripGtk::ActiveTabChanged(WebContents
* old_contents
,
1052 WebContents
* new_contents
,
1055 TRACE_EVENT0("ui::gtk", "TabStripGtk::ActiveTabChanged");
1059 void TabStripGtk::TabSelectionChanged(TabStripModel
* tab_strip_model
,
1060 const ui::ListSelectionModel
& old_model
) {
1061 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
1062 // a different size to the selected ones.
1063 bool tiny_tabs
= current_unselected_width_
!= current_selected_width_
;
1064 if (!IsAnimating() && (!needs_resize_layout_
|| tiny_tabs
))
1067 if (model_
->active_index() >= 0)
1068 GetTabAt(model_
->active_index())->SchedulePaint();
1070 if (old_model
.active() >= 0) {
1071 GetTabAt(old_model
.active())->SchedulePaint();
1072 GetTabAt(old_model
.active())->StopMiniTabTitleAnimation();
1075 std::vector
<int> indices_affected
;
1076 std::insert_iterator
<std::vector
<int> > it1(indices_affected
,
1077 indices_affected
.begin());
1078 std::set_symmetric_difference(
1079 old_model
.selected_indices().begin(),
1080 old_model
.selected_indices().end(),
1081 model_
->selection_model().selected_indices().begin(),
1082 model_
->selection_model().selected_indices().end(),
1084 for (std::vector
<int>::iterator it
= indices_affected
.begin();
1085 it
!= indices_affected
.end(); ++it
) {
1086 // SchedulePaint() has already been called for the active tab and
1087 // the previously active tab (if it still exists).
1088 if (*it
!= model_
->active_index() && *it
!= old_model
.active())
1089 GetTabAtAdjustForAnimation(*it
)->SchedulePaint();
1092 ui::ListSelectionModel::SelectedIndices no_longer_selected
=
1093 base::STLSetDifference
<ui::ListSelectionModel::SelectedIndices
>(
1094 old_model
.selected_indices(),
1095 model_
->selection_model().selected_indices());
1096 for (std::vector
<int>::iterator it
= no_longer_selected
.begin();
1097 it
!= no_longer_selected
.end(); ++it
) {
1098 GetTabAtAdjustForAnimation(*it
)->StopMiniTabTitleAnimation();
1102 void TabStripGtk::TabMoved(WebContents
* contents
,
1105 gfx::Rect start_bounds
= GetIdealBounds(from_index
);
1106 TabGtk
* tab
= GetTabAt(from_index
);
1107 tab_data_
.erase(tab_data_
.begin() + from_index
);
1108 TabData data
= {tab
, gfx::Rect()};
1109 tab
->set_mini(model_
->IsMiniTab(to_index
));
1110 tab
->SetBlocked(model_
->IsTabBlocked(to_index
));
1111 tab_data_
.insert(tab_data_
.begin() + to_index
, data
);
1112 GenerateIdealBounds();
1113 StartMoveTabAnimation(from_index
, to_index
);
1117 void TabStripGtk::TabChangedAt(WebContents
* contents
,
1119 TabChangeType change_type
) {
1120 // Index is in terms of the model. Need to make sure we adjust that index in
1121 // case we have an animation going.
1122 TabGtk
* tab
= GetTabAtAdjustForAnimation(index
);
1123 if (change_type
== TITLE_NOT_LOADING
) {
1124 if (tab
->mini() && !tab
->IsActive())
1125 tab
->StartMiniTabTitleAnimation();
1126 // We'll receive another notification of the change asynchronously.
1129 tab
->UpdateData(contents
,
1130 model_
->IsAppTab(index
),
1131 change_type
== LOADING_ONLY
);
1132 tab
->UpdateFromModel();
1135 void TabStripGtk::TabReplacedAt(TabStripModel
* tab_strip_model
,
1136 WebContents
* old_contents
,
1137 WebContents
* new_contents
,
1139 TabChangedAt(new_contents
, index
, ALL
);
1142 void TabStripGtk::TabMiniStateChanged(WebContents
* contents
, int index
) {
1143 // Don't do anything if we've already picked up the change from TabMoved.
1144 if (GetTabAt(index
)->mini() == model_
->IsMiniTab(index
))
1147 GetTabAt(index
)->set_mini(model_
->IsMiniTab(index
));
1148 // Don't animate if the window isn't visible yet. The window won't be visible
1149 // when dragging a mini-tab to a new window.
1150 if (window_
&& window_
->window() &&
1151 gtk_widget_get_visible(GTK_WIDGET(window_
->window()))) {
1152 StartMiniTabAnimation(index
);
1158 void TabStripGtk::TabBlockedStateChanged(WebContents
* contents
, int index
) {
1159 GetTabAt(index
)->SetBlocked(model_
->IsTabBlocked(index
));
1162 ////////////////////////////////////////////////////////////////////////////////
1163 // TabStripGtk, TabGtk::TabDelegate implementation:
1165 bool TabStripGtk::IsTabActive(const TabGtk
* tab
) const {
1169 return GetIndexOfTab(tab
) == model_
->active_index();
1172 bool TabStripGtk::IsTabSelected(const TabGtk
* tab
) const {
1176 return model_
->IsTabSelected(GetIndexOfTab(tab
));
1179 bool TabStripGtk::IsTabDetached(const TabGtk
* tab
) const {
1180 if (drag_controller_
.get())
1181 return drag_controller_
->IsTabDetached(tab
);
1185 void TabStripGtk::GetCurrentTabWidths(double* unselected_width
,
1186 double* selected_width
) const {
1187 *unselected_width
= current_unselected_width_
;
1188 *selected_width
= current_selected_width_
;
1191 bool TabStripGtk::IsTabPinned(const TabGtk
* tab
) const {
1195 return model_
->IsTabPinned(GetIndexOfTab(tab
));
1198 void TabStripGtk::ActivateTab(TabGtk
* tab
) {
1199 int index
= GetIndexOfTab(tab
);
1200 if (model_
->ContainsIndex(index
))
1201 model_
->ActivateTabAt(index
, true);
1204 void TabStripGtk::ToggleTabSelection(TabGtk
* tab
) {
1205 int index
= GetIndexOfTab(tab
);
1206 model_
->ToggleSelectionAt(index
);
1209 void TabStripGtk::ExtendTabSelection(TabGtk
* tab
) {
1210 int index
= GetIndexOfTab(tab
);
1211 if (model_
->ContainsIndex(index
))
1212 model_
->ExtendSelectionTo(index
);
1215 void TabStripGtk::CloseTab(TabGtk
* tab
) {
1216 int tab_index
= GetIndexOfTab(tab
);
1217 if (model_
->ContainsIndex(tab_index
)) {
1218 TabGtk
* last_tab
= GetTabAt(GetTabCount() - 1);
1219 // Limit the width available to the TabStrip for laying out Tabs, so that
1220 // Tabs are not resized until a later time (when the mouse pointer leaves
1222 available_width_for_tabs_
= GetAvailableWidthForTabs(last_tab
);
1223 needs_resize_layout_
= true;
1224 // We hook into the message loop in order to receive mouse move events when
1225 // the mouse is outside of the tabstrip. We unhook once the resize layout
1226 // animation is started.
1227 AddMessageLoopObserver();
1228 model_
->CloseWebContentsAt(tab_index
,
1229 TabStripModel::CLOSE_USER_GESTURE
|
1230 TabStripModel::CLOSE_CREATE_HISTORICAL_TAB
);
1234 bool TabStripGtk::IsCommandEnabledForTab(
1235 TabStripModel::ContextMenuCommand command_id
, const TabGtk
* tab
) const {
1236 int index
= GetIndexOfTab(tab
);
1237 if (model_
->ContainsIndex(index
))
1238 return model_
->IsContextMenuCommandEnabled(index
, command_id
);
1242 void TabStripGtk::ExecuteCommandForTab(
1243 TabStripModel::ContextMenuCommand command_id
, TabGtk
* tab
) {
1244 int index
= GetIndexOfTab(tab
);
1245 if (model_
->ContainsIndex(index
))
1246 model_
->ExecuteContextMenuCommand(index
, command_id
);
1249 void TabStripGtk::StartHighlightTabsForCommand(
1250 TabStripModel::ContextMenuCommand command_id
, TabGtk
* tab
) {
1251 if (command_id
== TabStripModel::CommandCloseOtherTabs
||
1252 command_id
== TabStripModel::CommandCloseTabsToRight
) {
1257 void TabStripGtk::StopHighlightTabsForCommand(
1258 TabStripModel::ContextMenuCommand command_id
, TabGtk
* tab
) {
1259 if (command_id
== TabStripModel::CommandCloseTabsToRight
||
1260 command_id
== TabStripModel::CommandCloseOtherTabs
) {
1261 // Just tell all Tabs to stop pulsing - it's safe.
1262 StopAllHighlighting();
1266 void TabStripGtk::StopAllHighlighting() {
1267 // TODO(jhawkins): Hook up animations.
1271 void TabStripGtk::MaybeStartDrag(TabGtk
* tab
, const gfx::Point
& point
) {
1272 // Don't accidentally start any drag operations during animations if the
1274 if (IsAnimating() || tab
->closing() || !HasAvailableDragActions())
1277 std::vector
<TabGtk
*> tabs
;
1278 for (size_t i
= 0; i
< model()->selection_model().size(); i
++) {
1279 TabGtk
* selected_tab
=
1280 GetTabAtAdjustForAnimation(
1281 model()->selection_model().selected_indices()[i
]);
1282 if (!selected_tab
->closing())
1283 tabs
.push_back(selected_tab
);
1286 drag_controller_
.reset(new DraggedTabControllerGtk(this, tab
, tabs
));
1287 drag_controller_
->CaptureDragInfo(point
);
1290 void TabStripGtk::ContinueDrag(GdkDragContext
* context
) {
1291 // We can get called even if |MaybeStartDrag| wasn't called in the event of
1292 // a TabStrip animation when the mouse button is down. In this case we should
1293 // _not_ continue the drag because it can lead to weird bugs.
1294 if (drag_controller_
.get())
1295 drag_controller_
->Drag();
1298 bool TabStripGtk::EndDrag(bool canceled
) {
1299 return drag_controller_
.get() ? drag_controller_
->EndDrag(canceled
) : false;
1302 bool TabStripGtk::HasAvailableDragActions() const {
1303 return model_
->delegate()->GetDragActions() != 0;
1306 GtkThemeService
* TabStripGtk::GetThemeProvider() {
1307 return theme_service_
;
1310 TabStripMenuController
* TabStripGtk::GetTabStripMenuControllerForTab(
1312 return new TabStripMenuController(tab
, model(), GetIndexOfTab(tab
));
1315 ///////////////////////////////////////////////////////////////////////////////
1316 // TabStripGtk, MessageLoop::Observer implementation:
1318 void TabStripGtk::WillProcessEvent(GdkEvent
* event
) {
1322 void TabStripGtk::DidProcessEvent(GdkEvent
* event
) {
1323 switch (event
->type
) {
1324 case GDK_MOTION_NOTIFY
:
1325 case GDK_LEAVE_NOTIFY
:
1326 HandleGlobalMouseMoveEvent();
1333 ///////////////////////////////////////////////////////////////////////////////
1334 // TabStripGtk, content::NotificationObserver implementation:
1336 void TabStripGtk::Observe(int type
,
1337 const content::NotificationSource
& source
,
1338 const content::NotificationDetails
& details
) {
1339 DCHECK_EQ(type
, chrome::NOTIFICATION_BROWSER_THEME_CHANGED
);
1340 SetNewTabButtonBackground();
1343 ////////////////////////////////////////////////////////////////////////////////
1344 // TabStripGtk, private:
1346 int TabStripGtk::GetTabCount() const {
1347 return static_cast<int>(tab_data_
.size());
1350 int TabStripGtk::GetMiniTabCount() const {
1352 for (size_t i
= 0; i
< tab_data_
.size(); ++i
) {
1353 if (tab_data_
[i
].tab
->mini())
1361 int TabStripGtk::GetAvailableWidthForTabs(TabGtk
* last_tab
) const {
1362 if (!base::i18n::IsRTL())
1363 return last_tab
->x() - bounds_
.x() + last_tab
->width();
1365 return bounds_
.width() - last_tab
->x();
1368 int TabStripGtk::GetIndexOfTab(const TabGtk
* tab
) const {
1369 for (int i
= 0, index
= 0; i
< GetTabCount(); ++i
, ++index
) {
1370 TabGtk
* current_tab
= GetTabAt(i
);
1371 if (current_tab
->closing()) {
1373 } else if (current_tab
== tab
) {
1380 TabGtk
* TabStripGtk::GetTabAt(int index
) const {
1381 DCHECK_GE(index
, 0);
1382 DCHECK_LT(index
, GetTabCount());
1383 return tab_data_
.at(index
).tab
;
1386 TabGtk
* TabStripGtk::GetTabAtAdjustForAnimation(int index
) const {
1387 if (active_animation_
.get() &&
1388 active_animation_
->type() == TabAnimation::REMOVE
&&
1390 static_cast<RemoveTabAnimation
*>(active_animation_
.get())->index()) {
1393 return GetTabAt(index
);
1396 void TabStripGtk::RemoveTabAt(int index
) {
1397 TabGtk
* removed
= tab_data_
.at(index
).tab
;
1399 // Remove the Tab from the TabStrip's list.
1400 tab_data_
.erase(tab_data_
.begin() + index
);
1402 if (!removed
->dragging()) {
1403 gtk_container_remove(GTK_CONTAINER(tabstrip_
.get()), removed
->widget());
1408 void TabStripGtk::HandleGlobalMouseMoveEvent() {
1409 if (!IsCursorInTabStripZone()) {
1410 // Mouse moved outside the tab slop zone, start a timer to do a resize
1411 // layout after a short while...
1412 if (!weak_factory_
.HasWeakPtrs()) {
1413 base::MessageLoop::current()->PostDelayedTask(
1415 base::Bind(&TabStripGtk::ResizeLayoutTabs
,
1416 weak_factory_
.GetWeakPtr()),
1417 base::TimeDelta::FromMilliseconds(kResizeTabsTimeMs
));
1420 // Mouse moved quickly out of the tab strip and then into it again, so
1421 // cancel the timer so that the strip doesn't move when the mouse moves
1423 weak_factory_
.InvalidateWeakPtrs();
1427 void TabStripGtk::GenerateIdealBounds() {
1428 int tab_count
= GetTabCount();
1429 double unselected
, selected
;
1430 GetDesiredTabWidths(tab_count
, GetMiniTabCount(), &unselected
, &selected
);
1432 current_unselected_width_
= unselected
;
1433 current_selected_width_
= selected
;
1435 // NOTE: This currently assumes a tab's height doesn't differ based on
1436 // selected state or the number of tabs in the strip!
1437 int tab_height
= TabGtk::GetStandardSize().height();
1438 double tab_x
= tab_start_x();
1439 for (int i
= 0; i
< tab_count
; ++i
) {
1440 TabGtk
* tab
= GetTabAt(i
);
1441 double tab_width
= unselected
;
1443 tab_width
= TabGtk::GetMiniWidth();
1444 else if (tab
->IsActive())
1445 tab_width
= selected
;
1446 double end_of_tab
= tab_x
+ tab_width
;
1447 int rounded_tab_x
= Round(tab_x
);
1448 gfx::Rect
state(rounded_tab_x
, 0, Round(end_of_tab
) - rounded_tab_x
,
1450 tab_data_
.at(i
).ideal_bounds
= state
;
1451 tab_x
= end_of_tab
+ GetTabHOffset(i
+ 1);
1455 void TabStripGtk::LayoutNewTabButton(double last_tab_right
,
1456 double unselected_width
) {
1457 GtkWidget
* toplevel
= gtk_widget_get_ancestor(widget(), GTK_TYPE_WINDOW
);
1458 bool is_maximized
= false;
1460 GdkWindow
* gdk_window
= gtk_widget_get_window(toplevel
);
1461 is_maximized
= (gdk_window_get_state(gdk_window
) &
1462 GDK_WINDOW_STATE_MAXIMIZED
) != 0;
1465 int y
= is_maximized
? 0 : kNewTabButtonVOffset
;
1466 int height
= newtab_surface_bounds_
.height() + kNewTabButtonVOffset
- y
;
1468 gfx::Rect
bounds(0, y
, newtab_surface_bounds_
.width(), height
);
1469 int delta
= abs(Round(unselected_width
) - TabGtk::GetStandardSize().width());
1470 if (delta
> 1 && !needs_resize_layout_
) {
1471 // We're shrinking tabs, so we need to anchor the New Tab button to the
1472 // right edge of the TabStrip's bounds, rather than the right edge of the
1473 // right-most Tab, otherwise it'll bounce when animating.
1474 bounds
.set_x(bounds_
.width() - newtab_button_
->WidgetAllocation().width
);
1476 bounds
.set_x(Round(last_tab_right
- kTabHOffset
) + kNewTabButtonHOffset
);
1478 bounds
.set_x(gtk_util::MirroredLeftPointForRect(tabstrip_
.get(), bounds
));
1480 gtk_fixed_move(GTK_FIXED(tabstrip_
.get()), newtab_button_
->widget(),
1481 bounds
.x(), bounds
.y());
1482 gtk_widget_set_size_request(newtab_button_
->widget(), bounds
.width(),
1486 void TabStripGtk::GetDesiredTabWidths(int tab_count
,
1488 double* unselected_width
,
1489 double* selected_width
) const {
1490 DCHECK(tab_count
>= 0 && mini_tab_count
>= 0 && mini_tab_count
<= tab_count
);
1491 const double min_unselected_width
=
1492 TabGtk::GetMinimumUnselectedSize().width();
1493 const double min_selected_width
=
1494 TabGtk::GetMinimumSelectedSize().width();
1496 *unselected_width
= min_unselected_width
;
1497 *selected_width
= min_selected_width
;
1499 if (tab_count
== 0) {
1500 // Return immediately to avoid divide-by-zero below.
1504 // Determine how much space we can actually allocate to tabs.
1505 GtkAllocation tabstrip_allocation
;
1506 gtk_widget_get_allocation(tabstrip_
.get(), &tabstrip_allocation
);
1507 int available_width
= tabstrip_allocation
.width
;
1508 if (available_width_for_tabs_
< 0) {
1509 available_width
= bounds_
.width();
1511 (kNewTabButtonHOffset
+ newtab_button_
->WidgetAllocation().width
);
1513 // Interesting corner case: if |available_width_for_tabs_| > the result
1514 // of the calculation in the conditional arm above, the strip is in
1515 // overflow. We can either use the specified width or the true available
1516 // width here; the first preserves the consistent "leave the last tab under
1517 // the user's mouse so they can close many tabs" behavior at the cost of
1518 // prolonging the glitchy appearance of the overflow state, while the second
1519 // gets us out of overflow as soon as possible but forces the user to move
1520 // their mouse for a few tabs' worth of closing. We choose visual
1521 // imperfection over behavioral imperfection and select the first option.
1522 available_width
= available_width_for_tabs_
;
1525 if (mini_tab_count
> 0) {
1526 available_width
-= mini_tab_count
* (TabGtk::GetMiniWidth() + kTabHOffset
);
1527 tab_count
-= mini_tab_count
;
1528 if (tab_count
== 0) {
1529 *selected_width
= *unselected_width
= TabGtk::GetStandardSize().width();
1532 // Account for gap between the last mini-tab and first normal tab.
1533 available_width
-= mini_to_non_mini_gap_
;
1536 // Calculate the desired tab widths by dividing the available space into equal
1537 // portions. Don't let tabs get larger than the "standard width" or smaller
1538 // than the minimum width for each type, respectively.
1539 const int total_offset
= kTabHOffset
* (tab_count
- 1);
1540 const double desired_tab_width
= std::min(
1541 (static_cast<double>(available_width
- total_offset
) /
1542 static_cast<double>(tab_count
)),
1543 static_cast<double>(TabGtk::GetStandardSize().width()));
1545 *unselected_width
= std::max(desired_tab_width
, min_unselected_width
);
1546 *selected_width
= std::max(desired_tab_width
, min_selected_width
);
1548 // When there are multiple tabs, we'll have one selected and some unselected
1549 // tabs. If the desired width was between the minimum sizes of these types,
1550 // try to shrink the tabs with the smaller minimum. For example, if we have
1551 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If
1552 // selected tabs have a minimum width of 4 and unselected tabs have a minimum
1553 // width of 1, the above code would set *unselected_width = 2.5,
1554 // *selected_width = 4, which results in a total width of 11.5. Instead, we
1555 // want to set *unselected_width = 2, *selected_width = 4, for a total width
1557 if (tab_count
> 1) {
1558 if ((min_unselected_width
< min_selected_width
) &&
1559 (desired_tab_width
< min_selected_width
)) {
1561 static_cast<double>(
1562 available_width
- total_offset
- min_selected_width
) /
1563 static_cast<double>(tab_count
- 1);
1564 *unselected_width
= std::max(calc_width
, min_unselected_width
);
1565 } else if ((min_unselected_width
> min_selected_width
) &&
1566 (desired_tab_width
< min_unselected_width
)) {
1567 *selected_width
= std::max(available_width
- total_offset
-
1568 (min_unselected_width
* (tab_count
- 1)), min_selected_width
);
1573 int TabStripGtk::GetTabHOffset(int tab_index
) {
1574 if (tab_index
< GetTabCount() && GetTabAt(tab_index
- 1)->mini() &&
1575 !GetTabAt(tab_index
)->mini()) {
1576 return mini_to_non_mini_gap_
+ kTabHOffset
;
1581 int TabStripGtk::tab_start_x() const {
1585 void TabStripGtk::ResizeLayoutTabs() {
1586 weak_factory_
.InvalidateWeakPtrs();
1587 layout_factory_
.InvalidateWeakPtrs();
1589 // It is critically important that this is unhooked here, otherwise we will
1590 // keep spying on messages forever.
1591 RemoveMessageLoopObserver();
1593 available_width_for_tabs_
= -1;
1594 int mini_tab_count
= GetMiniTabCount();
1595 if (mini_tab_count
== GetTabCount()) {
1596 // Only mini tabs, we know the tab widths won't have changed (all mini-tabs
1597 // have the same width), so there is nothing to do.
1600 TabGtk
* first_tab
= GetTabAt(mini_tab_count
);
1601 double unselected
, selected
;
1602 GetDesiredTabWidths(GetTabCount(), mini_tab_count
, &unselected
, &selected
);
1603 int w
= Round(first_tab
->IsActive() ? selected
: unselected
);
1605 // We only want to run the animation if we're not already at the desired
1607 if (abs(first_tab
->width() - w
) > 1)
1608 StartResizeLayoutAnimation();
1611 bool TabStripGtk::IsCursorInTabStripZone() const {
1612 gfx::Point tabstrip_topleft
;
1613 gtk_util::ConvertWidgetPointToScreen(tabstrip_
.get(), &tabstrip_topleft
);
1615 gfx::Rect bds
= bounds();
1616 bds
.set_origin(tabstrip_topleft
);
1617 bds
.set_height(bds
.height() + kTabStripAnimationVSlop
);
1619 GdkScreen
* screen
= gdk_screen_get_default();
1620 GdkDisplay
* display
= gdk_screen_get_display(screen
);
1622 gdk_display_get_pointer(display
, NULL
, &x
, &y
, NULL
);
1623 gfx::Point
cursor_point(x
, y
);
1625 return bds
.Contains(cursor_point
);
1628 void TabStripGtk::ReStack() {
1629 TRACE_EVENT0("ui::gtk", "TabStripGtk::ReStack");
1631 if (!gtk_widget_get_realized(tabstrip_
.get())) {
1632 // If the window isn't realized yet, we can't stack them yet. It will be
1633 // done by the OnMap signal handler.
1636 int tab_count
= GetTabCount();
1637 TabGtk
* active_tab
= NULL
;
1638 for (int i
= tab_count
- 1; i
>= 0; --i
) {
1639 TabGtk
* tab
= GetTabAt(i
);
1640 if (tab
->IsActive())
1646 active_tab
->Raise();
1649 void TabStripGtk::AddMessageLoopObserver() {
1650 if (!added_as_message_loop_observer_
) {
1651 base::MessageLoopForUI::current()->AddObserver(this);
1652 added_as_message_loop_observer_
= true;
1656 void TabStripGtk::RemoveMessageLoopObserver() {
1657 if (added_as_message_loop_observer_
) {
1658 base::MessageLoopForUI::current()->RemoveObserver(this);
1659 added_as_message_loop_observer_
= false;
1663 gfx::Rect
TabStripGtk::GetDropBounds(int drop_index
,
1666 DCHECK_NE(drop_index
, -1);
1668 if (drop_index
< GetTabCount()) {
1669 TabGtk
* tab
= GetTabAt(drop_index
);
1670 gfx::Rect bounds
= tab
->GetNonMirroredBounds(tabstrip_
.get());
1671 // TODO(sky): update these for pinned tabs.
1673 center_x
= bounds
.x() - (kTabHOffset
/ 2);
1675 center_x
= bounds
.x() + (bounds
.width() / 2);
1677 TabGtk
* last_tab
= GetTabAt(drop_index
- 1);
1678 gfx::Rect bounds
= last_tab
->GetNonMirroredBounds(tabstrip_
.get());
1679 center_x
= bounds
.x() + bounds
.width() + (kTabHOffset
/ 2);
1682 center_x
= gtk_util::MirroredXCoordinate(tabstrip_
.get(), center_x
);
1684 // Determine the screen bounds.
1685 gfx::Point
drop_loc(center_x
- drop_indicator_width
/ 2,
1686 -drop_indicator_height
);
1687 gtk_util::ConvertWidgetPointToScreen(tabstrip_
.get(), &drop_loc
);
1688 gfx::Rect
drop_bounds(drop_loc
.x(), drop_loc
.y(), drop_indicator_width
,
1689 drop_indicator_height
);
1691 // TODO(jhawkins): We always display the arrow underneath the tab because we
1692 // don't have custom frame support yet.
1695 drop_bounds
.Offset(0, drop_bounds
.height() + bounds().height());
1700 void TabStripGtk::UpdateDropIndex(GdkDragContext
* context
, gint x
, gint y
) {
1701 // If the UI layout is right-to-left, we need to mirror the mouse
1702 // coordinates since we calculate the drop index based on the
1703 // original (and therefore non-mirrored) positions of the tabs.
1704 x
= gtk_util::MirroredXCoordinate(tabstrip_
.get(), x
);
1705 // We don't allow replacing the urls of mini-tabs.
1706 for (int i
= GetMiniTabCount(); i
< GetTabCount(); ++i
) {
1707 TabGtk
* tab
= GetTabAt(i
);
1708 gfx::Rect bounds
= tab
->GetNonMirroredBounds(tabstrip_
.get());
1709 const int tab_max_x
= bounds
.x() + bounds
.width();
1710 const int hot_width
= bounds
.width() / kTabEdgeRatioInverse
;
1711 if (x
< tab_max_x
) {
1712 if (x
< bounds
.x() + hot_width
)
1713 SetDropIndex(i
, true);
1714 else if (x
>= tab_max_x
- hot_width
)
1715 SetDropIndex(i
+ 1, true);
1717 SetDropIndex(i
, false);
1722 // The drop isn't over a tab, add it to the end.
1723 SetDropIndex(GetTabCount(), true);
1726 void TabStripGtk::SetDropIndex(int index
, bool drop_before
) {
1728 gfx::Rect drop_bounds
= GetDropBounds(index
, drop_before
, &is_beneath
);
1730 // Perform a delayed tab transition if hovering directly over a tab;
1731 // otherwise, cancel the pending one.
1732 if (index
!= -1 && !drop_before
)
1733 hover_tab_selector_
.StartTabTransition(index
);
1735 hover_tab_selector_
.CancelTabTransition();
1737 if (!drop_info_
.get()) {
1738 drop_info_
.reset(new DropInfo(index
, drop_before
, !is_beneath
));
1740 if (!GTK_IS_WIDGET(drop_info_
->container
)) {
1741 drop_info_
->CreateContainer();
1742 } else if (drop_info_
->drop_index
== index
&&
1743 drop_info_
->drop_before
== drop_before
) {
1747 drop_info_
->drop_index
= index
;
1748 drop_info_
->drop_before
= drop_before
;
1749 if (is_beneath
== drop_info_
->point_down
) {
1750 drop_info_
->point_down
= !is_beneath
;
1751 drop_info_
->drop_arrow
= GetDropArrowImage(drop_info_
->point_down
);
1755 gtk_window_move(GTK_WINDOW(drop_info_
->container
),
1756 drop_bounds
.x(), drop_bounds
.y());
1757 gtk_window_resize(GTK_WINDOW(drop_info_
->container
),
1758 drop_bounds
.width(), drop_bounds
.height());
1761 bool TabStripGtk::CompleteDrop(const guchar
* data
, bool is_plain_text
) {
1762 if (!drop_info_
.get())
1765 const int drop_index
= drop_info_
->drop_index
;
1766 const bool drop_before
= drop_info_
->drop_before
;
1768 // Destroy the drop indicator.
1771 // Cancel any pending tab transition.
1772 hover_tab_selector_
.CancelTabTransition();
1775 if (is_plain_text
) {
1776 AutocompleteMatch match
;
1777 AutocompleteClassifierFactory::GetForProfile(model_
->profile())->Classify(
1778 base::UTF8ToUTF16(reinterpret_cast<const char*>(data
)),
1779 false, false, &match
, NULL
);
1780 url
= match
.destination_url
;
1782 std::string
url_string(reinterpret_cast<const char*>(data
));
1783 url
= GURL(url_string
.substr(0, url_string
.find_first_of('\n')));
1785 if (!url
.is_valid())
1788 chrome::NavigateParams
params(window()->browser(), url
,
1789 content::PAGE_TRANSITION_LINK
);
1790 params
.tabstrip_index
= drop_index
;
1793 params
.disposition
= NEW_FOREGROUND_TAB
;
1795 params
.disposition
= CURRENT_TAB
;
1796 params
.source_contents
= model_
->GetWebContentsAt(drop_index
);
1799 chrome::Navigate(¶ms
);
1805 gfx::Image
* TabStripGtk::GetDropArrowImage(bool is_down
) {
1806 return &ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
1807 is_down
? IDR_TAB_DROP_DOWN
: IDR_TAB_DROP_UP
);
1810 // TabStripGtk::DropInfo -------------------------------------------------------
1812 TabStripGtk::DropInfo::DropInfo(int drop_index
, bool drop_before
,
1814 : drop_index(drop_index
),
1815 drop_before(drop_before
),
1816 point_down(point_down
) {
1818 drop_arrow
= GetDropArrowImage(point_down
);
1821 TabStripGtk::DropInfo::~DropInfo() {
1825 gboolean
TabStripGtk::DropInfo::OnExposeEvent(GtkWidget
* widget
,
1826 GdkEventExpose
* event
) {
1827 TRACE_EVENT0("ui::gtk", "TabStripGtk::DropInfo::OnExposeEvent");
1829 if (ui::IsScreenComposited()) {
1830 SetContainerTransparency();
1832 SetContainerShapeMask();
1835 cairo_t
* cr
= gdk_cairo_create(gtk_widget_get_window(widget
));
1836 gdk_cairo_rectangle(cr
, &event
->area
);
1839 drop_arrow
->ToCairo()->SetSource(cr
, widget
, 0, 0);
1847 // Sets the color map of the container window to allow the window to be
1849 void TabStripGtk::DropInfo::SetContainerColorMap() {
1850 GdkScreen
* screen
= gtk_widget_get_screen(container
);
1851 GdkColormap
* colormap
= gdk_screen_get_rgba_colormap(screen
);
1853 // If rgba is not available, use rgb instead.
1855 colormap
= gdk_screen_get_rgb_colormap(screen
);
1857 gtk_widget_set_colormap(container
, colormap
);
1860 // Sets full transparency for the container window. This is used if
1861 // compositing is available for the screen.
1862 void TabStripGtk::DropInfo::SetContainerTransparency() {
1863 cairo_t
* cairo_context
= gdk_cairo_create(gtk_widget_get_window(container
));
1867 // Make the background of the dragged tab window fully transparent. All of
1868 // the content of the window (child widgets) will be completely opaque.
1870 cairo_scale(cairo_context
, static_cast<double>(drop_indicator_width
),
1871 static_cast<double>(drop_indicator_height
));
1872 cairo_set_source_rgba(cairo_context
, 1.0f
, 1.0f
, 1.0f
, 0.0f
);
1873 cairo_set_operator(cairo_context
, CAIRO_OPERATOR_SOURCE
);
1874 cairo_paint(cairo_context
);
1875 cairo_destroy(cairo_context
);
1878 // Sets the shape mask for the container window to emulate a transparent
1879 // container window. This is used if compositing is not available for the
1881 void TabStripGtk::DropInfo::SetContainerShapeMask() {
1882 // Create a 1bpp bitmap the size of |container|.
1883 GdkPixmap
* pixmap
= gdk_pixmap_new(NULL
,
1884 drop_indicator_width
,
1885 drop_indicator_height
, 1);
1886 cairo_t
* cairo_context
= gdk_cairo_create(GDK_DRAWABLE(pixmap
));
1888 // Set the transparency.
1889 cairo_set_source_rgba(cairo_context
, 1, 1, 1, 0);
1891 // Blit the rendered bitmap into a pixmap. Any pixel set in the pixmap will
1892 // be opaque in the container window.
1893 cairo_set_operator(cairo_context
, CAIRO_OPERATOR_SOURCE
);
1894 // We don't use CairoCachedSurface::SetSource() here because we're not
1895 // rendering on a display server.
1896 gdk_cairo_set_source_pixbuf(cairo_context
, drop_arrow
->ToGdkPixbuf(), 0, 0);
1897 cairo_paint(cairo_context
);
1898 cairo_destroy(cairo_context
);
1900 // Set the shape mask.
1901 GdkWindow
* gdk_window
= gtk_widget_get_window(container
);
1902 gdk_window_shape_combine_mask(gdk_window
, pixmap
, 0, 0);
1903 g_object_unref(pixmap
);
1906 void TabStripGtk::DropInfo::CreateContainer() {
1907 container
= gtk_window_new(GTK_WINDOW_POPUP
);
1908 SetContainerColorMap();
1909 gtk_widget_set_app_paintable(container
, TRUE
);
1910 g_signal_connect(container
, "expose-event",
1911 G_CALLBACK(OnExposeEventThunk
), this);
1912 gtk_widget_add_events(container
, GDK_STRUCTURE_MASK
);
1913 gtk_window_move(GTK_WINDOW(container
), 0, 0);
1914 gtk_window_resize(GTK_WINDOW(container
),
1915 drop_indicator_width
, drop_indicator_height
);
1916 gtk_widget_show_all(container
);
1919 void TabStripGtk::DropInfo::DestroyContainer() {
1920 if (GTK_IS_WIDGET(container
))
1921 gtk_widget_destroy(container
);
1924 void TabStripGtk::StopAnimation() {
1925 if (active_animation_
.get())
1926 active_animation_
->Stop();
1931 void TabStripGtk::AnimationLayout(double unselected_width
) {
1932 int tab_height
= TabGtk::GetStandardSize().height();
1933 double tab_x
= tab_start_x();
1934 for (int i
= 0; i
< GetTabCount(); ++i
) {
1935 TabAnimation
* animation
= active_animation_
.get();
1937 tab_x
+= animation
->GetGapWidth(i
);
1938 double tab_width
= TabAnimation::GetCurrentTabWidth(this, animation
, i
);
1939 double end_of_tab
= tab_x
+ tab_width
;
1940 int rounded_tab_x
= Round(tab_x
);
1941 TabGtk
* tab
= GetTabAt(i
);
1942 gfx::Rect
bounds(rounded_tab_x
, 0, Round(end_of_tab
) - rounded_tab_x
,
1944 SetTabBounds(tab
, bounds
);
1945 tab_x
= end_of_tab
+ GetTabHOffset(i
+ 1);
1947 LayoutNewTabButton(tab_x
, unselected_width
);
1950 void TabStripGtk::StartInsertTabAnimation(int index
) {
1951 // The TabStrip can now use its entire width to lay out Tabs.
1952 available_width_for_tabs_
= -1;
1954 active_animation_
.reset(new InsertTabAnimation(this, index
));
1955 active_animation_
->Start();
1958 void TabStripGtk::StartRemoveTabAnimation(int index
, WebContents
* contents
) {
1959 if (active_animation_
.get()) {
1960 // Some animations (e.g. MoveTabAnimation) cause there to be a Layout when
1961 // they're completed (which includes canceled). Since |tab_data_| is now
1962 // inconsistent with TabStripModel, doing this Layout will crash now, so
1963 // we ask the MoveTabAnimation to skip its Layout (the state will be
1964 // corrected by the RemoveTabAnimation we're about to initiate).
1965 active_animation_
->set_layout_on_completion(false);
1966 active_animation_
->Stop();
1969 active_animation_
.reset(new RemoveTabAnimation(this, index
, contents
));
1970 active_animation_
->Start();
1973 void TabStripGtk::StartMoveTabAnimation(int from_index
, int to_index
) {
1975 active_animation_
.reset(new MoveTabAnimation(this, from_index
, to_index
));
1976 active_animation_
->Start();
1979 void TabStripGtk::StartResizeLayoutAnimation() {
1981 active_animation_
.reset(new ResizeLayoutAnimation(this));
1982 active_animation_
->Start();
1985 void TabStripGtk::StartMiniTabAnimation(int index
) {
1987 active_animation_
.reset(new MiniTabAnimation(this, index
));
1988 active_animation_
->Start();
1991 void TabStripGtk::StartMiniMoveTabAnimation(int from_index
,
1993 const gfx::Rect
& start_bounds
) {
1995 active_animation_
.reset(
1996 new MiniMoveAnimation(this, from_index
, to_index
, start_bounds
));
1997 active_animation_
->Start();
2000 void TabStripGtk::FinishAnimation(TabStripGtk::TabAnimation
* animation
,
2002 active_animation_
.reset(NULL
);
2004 // Reset the animation state of each tab.
2005 for (int i
= 0, count
= GetTabCount(); i
< count
; ++i
)
2006 GetTabAt(i
)->set_animating_mini_change(false);
2012 void TabStripGtk::OnMap(GtkWidget
* widget
) {
2016 gboolean
TabStripGtk::OnExpose(GtkWidget
* widget
, GdkEventExpose
* event
) {
2017 TRACE_EVENT0("ui::gtk", "TabStripGtk::OnExpose");
2019 if (gdk_region_empty(event
->region
))
2022 // If we're only repainting favicons, optimize the paint path and only draw
2024 GdkRectangle
* rects
;
2026 gdk_region_get_rectangles(event
->region
, &rects
, &num_rects
);
2027 qsort(rects
, num_rects
, sizeof(GdkRectangle
), CompareGdkRectangles
);
2028 std::vector
<int> tabs_to_repaint
;
2029 if (!IsDragSessionActive() &&
2030 CanPaintOnlyFavicons(rects
, num_rects
, &tabs_to_repaint
)) {
2031 PaintOnlyFavicons(event
, tabs_to_repaint
);
2037 // Ideally we'd like to only draw what's needed in the damage rect, but the
2038 // tab widgets overlap each other. To get the proper visual look, we need to
2039 // draw tabs from the rightmost to the leftmost tab. So if we have a dirty
2040 // rectangle in the center of the tabstrip, we'll have to draw all the tabs
2041 // to the left of it.
2043 // TODO(erg): Figure out why we can't have clip rects that don't start from
2044 // x=0. jhawkins had a big comment here about how painting on one widget will
2045 // cause an expose-event to be sent to the widgets underneath, but that
2046 // should still obey clip rects, but doesn't seem to.
2047 if (active_animation_
.get() || drag_controller_
.get()) {
2048 // If we have an active animation or the tab is being dragged, no matter
2049 // what GTK tells us our dirty rectangles are, we need to redraw the entire
2051 event
->area
.width
= bounds_
.width();
2053 // Expand whatever dirty rectangle we were given to the area from the
2054 // leftmost edge of the tabstrip to the rightmost edge of the dirty
2057 // Doing this frees up CPU when redrawing the tabstrip with throbbing
2058 // tabs. The most likely tabs to throb are pinned or minitabs which live on
2059 // the very leftmost of the tabstrip.
2060 event
->area
.width
+= event
->area
.x
;
2065 event
->area
.height
= bounds_
.height();
2066 gdk_region_union_with_rect(event
->region
, &event
->area
);
2068 // Paint the New Tab button.
2069 gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_
.get()),
2070 newtab_button_
->widget(), event
);
2072 // Paint the tabs in reverse order, so they stack to the left.
2073 TabGtk
* selected_tab
= NULL
;
2074 int tab_count
= GetTabCount();
2075 for (int i
= tab_count
- 1; i
>= 0; --i
) {
2076 TabGtk
* tab
= GetTabAt(i
);
2077 // We must ask the _Tab's_ model, not ourselves, because in some situations
2078 // the model will be different to this object, e.g. when a Tab is being
2079 // removed after its WebContents has been destroyed.
2080 if (!tab
->IsActive()) {
2081 gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_
.get()),
2082 tab
->widget(), event
);
2088 // Paint the selected tab last, so it overlaps all the others.
2090 gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_
.get()),
2091 selected_tab
->widget(), event
);
2097 void TabStripGtk::OnSizeAllocate(GtkWidget
* widget
, GtkAllocation
* allocation
) {
2098 TRACE_EVENT0("ui::gtk", "TabStripGtk::OnSizeAllocate");
2100 gfx::Rect bounds
= gfx::Rect(allocation
->x
, allocation
->y
,
2101 allocation
->width
, allocation
->height
);
2103 // Nothing to do if the bounds are the same. If we don't catch this, we'll
2104 // get an infinite loop of size-allocate signals.
2105 if (bounds_
== bounds
)
2110 // No tabs, nothing to layout. This happens when a browser window is created
2111 // and shown before tabs are added (as in a popup window).
2112 if (GetTabCount() == 0)
2115 // When there is only one tab, Layout() so we don't animate it. With more
2116 // tabs, we should always attempt a resize unless we already have one coming
2117 // up in our message loop.
2118 if (GetTabCount() == 1) {
2120 } else if (!layout_factory_
.HasWeakPtrs()) {
2121 base::MessageLoop::current()->PostDelayedTask(
2123 base::Bind(&TabStripGtk::Layout
, layout_factory_
.GetWeakPtr()),
2124 base::TimeDelta::FromMilliseconds(kLayoutAfterSizeAllocateMs
));
2128 gboolean
TabStripGtk::OnDragMotion(GtkWidget
* widget
, GdkDragContext
* context
,
2129 gint x
, gint y
, guint time
) {
2130 UpdateDropIndex(context
, x
, y
);
2134 gboolean
TabStripGtk::OnDragDrop(GtkWidget
* widget
, GdkDragContext
* context
,
2135 gint x
, gint y
, guint time
) {
2136 if (!drop_info_
.get())
2139 GdkAtom target
= gtk_drag_dest_find_target(widget
, context
, NULL
);
2140 if (target
!= GDK_NONE
)
2141 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
2143 gtk_drag_get_data(widget
, context
, target
, time
);
2148 gboolean
TabStripGtk::OnDragLeave(GtkWidget
* widget
, GdkDragContext
* context
,
2150 // Destroy the drop indicator.
2151 drop_info_
->DestroyContainer();
2153 // Cancel any pending tab transition.
2154 hover_tab_selector_
.CancelTabTransition();
2159 gboolean
TabStripGtk::OnDragDataReceived(GtkWidget
* widget
,
2160 GdkDragContext
* context
,
2162 GtkSelectionData
* data
,
2163 guint info
, guint time
) {
2164 bool success
= false;
2166 if (info
== ui::TEXT_URI_LIST
||
2167 info
== ui::NETSCAPE_URL
||
2168 info
== ui::TEXT_PLAIN
) {
2169 success
= CompleteDrop(gtk_selection_data_get_data(data
),
2170 info
== ui::TEXT_PLAIN
);
2173 gtk_drag_finish(context
, success
, FALSE
, time
);
2177 void TabStripGtk::OnNewTabClicked(GtkWidget
* widget
) {
2178 GdkEvent
* event
= gtk_get_current_event();
2179 DCHECK_EQ(event
->type
, GDK_BUTTON_RELEASE
);
2180 int mouse_button
= event
->button
.button
;
2181 gdk_event_free(event
);
2183 switch (mouse_button
) {
2185 content::RecordAction(UserMetricsAction("NewTab_Button"));
2186 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON
,
2187 TabStripModel::NEW_TAB_ENUM_COUNT
);
2188 model_
->delegate()->AddTabAt(GURL(), -1, true);
2191 // On middle-click, try to parse the PRIMARY selection as a URL and load
2192 // it instead of creating a blank page.
2194 if (!gtk_util::URLFromPrimarySelection(model_
->profile(), &url
))
2197 Browser
* browser
= window_
->browser();
2199 chrome::AddSelectedTabWithURL(
2200 browser
, url
, content::PAGE_TRANSITION_TYPED
);
2204 NOTREACHED() << "Got click on new tab button with unhandled mouse "
2205 << "button " << mouse_button
;
2209 void TabStripGtk::SetTabBounds(TabGtk
* tab
, const gfx::Rect
& bounds
) {
2210 gfx::Rect bds
= bounds
;
2211 bds
.set_x(gtk_util::MirroredLeftPointForRect(tabstrip_
.get(), bounds
));
2212 tab
->SetBounds(bds
);
2213 gtk_fixed_move(GTK_FIXED(tabstrip_
.get()), tab
->widget(),
2217 bool TabStripGtk::CanPaintOnlyFavicons(const GdkRectangle
* rects
,
2218 int num_rects
, std::vector
<int>* tabs_to_paint
) {
2219 // |rects| are sorted so we just need to scan from left to right and compare
2220 // it to the tab favicon positions from left to right.
2222 for (int r
= 0; r
< num_rects
; ++r
) {
2223 while (t
< GetTabCount()) {
2224 TabGtk
* tab
= GetTabAt(t
);
2225 if (GdkRectMatchesTabFaviconBounds(rects
[r
], tab
) &&
2226 tab
->ShouldShowIcon()) {
2227 tabs_to_paint
->push_back(t
);
2234 return static_cast<int>(tabs_to_paint
->size()) == num_rects
;
2237 void TabStripGtk::PaintOnlyFavicons(GdkEventExpose
* event
,
2238 const std::vector
<int>& tabs_to_paint
) {
2239 cairo_t
* cr
= gdk_cairo_create(GDK_DRAWABLE(event
->window
));
2240 for (size_t i
= 0; i
< tabs_to_paint
.size(); ++i
) {
2242 GetTabAt(tabs_to_paint
[i
])->PaintFaviconArea(tabstrip_
.get(), cr
);
2249 CustomDrawButton
* TabStripGtk::MakeNewTabButton() {
2250 CustomDrawButton
* button
= new CustomDrawButton(IDR_NEWTAB_BUTTON
,
2251 IDR_NEWTAB_BUTTON_P
, IDR_NEWTAB_BUTTON_H
, 0);
2253 gtk_widget_set_tooltip_text(button
->widget(),
2254 l10n_util::GetStringUTF8(IDS_TOOLTIP_NEW_TAB
).c_str());
2256 // Let the middle mouse button initiate clicks as well.
2257 gtk_util::SetButtonTriggersNavigation(button
->widget());
2258 g_signal_connect(button
->widget(), "clicked",
2259 G_CALLBACK(OnNewTabClickedThunk
), this);
2260 gtk_widget_set_can_focus(button
->widget(), FALSE
);
2261 gtk_fixed_put(GTK_FIXED(tabstrip_
.get()), button
->widget(), 0, 0);
2266 void TabStripGtk::SetNewTabButtonBackground() {
2267 SkColor color
= theme_service_
->GetColor(
2268 ThemeProperties::COLOR_BUTTON_BACKGROUND
);
2269 SkBitmap background
= theme_service_
->GetImageNamed(
2270 IDR_THEME_WINDOW_CONTROL_BACKGROUND
).AsBitmap();
2271 SkBitmap mask
= theme_service_
->GetImageNamed(
2272 IDR_NEWTAB_BUTTON_MASK
).AsBitmap();
2273 newtab_button_
->SetBackground(color
, background
, mask
);