Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / gtk / tabs / tab_strip_gtk.cc
blob6fb4e077ca9c87b92f652cb755ddba0ae7a82e50
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"
7 #include <gtk/gtk.h>
9 #include <algorithm>
10 #include <string>
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;
56 namespace {
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
67 // is started.
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
79 // tabs.
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;
108 if (p1_x < p2_x)
109 return -1;
110 else if (p1_x == p2_x)
111 return 0;
112 return 1;
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();
123 } // namespace
125 ////////////////////////////////////////////////////////////////////////////////
127 // TabAnimation
129 // A base class for all TabStrip animations.
131 class TabStripGtk::TabAnimation : public gfx::AnimationDelegate {
132 public:
133 friend class TabStripGtk;
135 // Possible types of animation.
136 enum Type {
137 INSERT,
138 REMOVE,
139 MOVE,
140 RESIZE,
141 MINI,
142 MINI_MOVE
145 TabAnimation(TabStripGtk* tabstrip, Type type)
146 : tabstrip_(tabstrip),
147 animation_(this),
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),
153 type_(type) {
155 virtual ~TabAnimation() {}
157 Type type() const { return type_; }
159 void Start() {
160 animation_.SetSlideDuration(GetDuration());
161 animation_.SetTweenType(gfx::Tween::EASE_OUT);
162 if (!animation_.IsShowing()) {
163 animation_.Reset();
164 animation_.Show();
168 void Stop() {
169 animation_.Stop();
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
177 // active.
178 static double GetCurrentTabWidth(TabStripGtk* tabstrip,
179 TabStripGtk::TabAnimation* animation,
180 int index) {
181 TabGtk* tab = tabstrip->GetTabAt(index);
182 double tab_width;
183 if (tab->mini()) {
184 tab_width = TabGtk::GetMiniWidth();
185 } else {
186 double unselected, selected;
187 tabstrip->GetCurrentTabWidths(&unselected, &selected);
188 tab_width = tab->IsActive() ? selected : unselected;
191 if (animation) {
192 double specified_tab_width = animation->GetWidthForTab(index);
193 if (specified_tab_width != -1)
194 tab_width = specified_tab_width;
197 return 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) {
217 return 0;
220 protected:
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
228 // used for the Tab.
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_;
265 private:
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_;
272 const Type type_;
274 DISALLOW_COPY_AND_ASSIGN(TabAnimation);
277 ////////////////////////////////////////////////////////////////////////////////
279 // Handles insertion of a Tab at |index|.
280 class InsertTabAnimation : public TabStripGtk::TabAnimation {
281 public:
282 InsertTabAnimation(TabStripGtk* tabstrip, int index)
283 : TabAnimation(tabstrip, INSERT),
284 index_(index) {
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)
289 start_mini_count--;
290 GenerateStartAndEndWidths(tab_count - 1, tab_count, start_mini_count,
291 end_mini_count);
293 virtual ~InsertTabAnimation() {}
295 protected:
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();
304 } else {
305 target_width =
306 is_selected ? end_unselected_width_ : end_selected_width_;
307 start_width =
308 is_selected ? TabGtk::GetMinimumSelectedSize().width() :
309 TabGtk::GetMinimumUnselectedSize().width();
312 double delta = target_width - start_width;
313 if (delta > 0)
314 return start_width + (delta * animation_.GetCurrentValue());
316 return start_width;
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());
331 private:
332 int index_;
334 DISALLOW_COPY_AND_ASSIGN(InsertTabAnimation);
337 ////////////////////////////////////////////////////////////////////////////////
339 // Handles removal of a Tab from |index|
340 class RemoveTabAnimation : public TabStripGtk::TabAnimation {
341 public:
342 RemoveTabAnimation(TabStripGtk* tabstrip, int index, WebContents* contents)
343 : TabAnimation(tabstrip, REMOVE),
344 index_(index) {
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)
349 end_mini_count--;
350 GenerateStartAndEndWidths(tab_count, tab_count - 1, start_mini_count,
351 end_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
356 // the removal.
357 // We do the same when the last mini-tab is being removed for the same
358 // reason.
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_; }
370 protected:
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
377 // of the animation.
378 if (tab->mini()) {
379 return animation_.CurrentValueBetween(TabGtk::GetMiniWidth(),
380 -kTabHOffset);
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);
393 if (tab->mini())
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);
417 private:
418 int index_;
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 {
427 public:
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 {
444 // Position Tab A
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(),
449 tab_a_->height());
450 tabstrip_->SetTabBounds(tab_a_, bounds);
452 // Position Tab B
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(),
457 tab_b_->height());
458 tabstrip_->SetTabBounds(tab_b_, bounds);
461 protected:
462 // Overridden from TabStripGtk::TabAnimation:
463 virtual int GetDuration() const OVERRIDE {
464 return kReorderAnimationDurationMs;
467 private:
468 // The two tabs being exchanged.
469 TabGtk* tab_a_;
470 TabGtk* tab_b_;
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
482 // to another.
483 class ResizeLayoutAnimation : public TabStripGtk::TabAnimation {
484 public:
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,
490 mini_tab_count);
491 InitStartState();
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);
501 protected:
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);
510 if (tab->mini())
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_);
522 private:
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
526 // behavior...
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();
533 } else {
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
544 // in the model.
545 class MiniTabAnimation : public TabStripGtk::TabAnimation {
546 public:
547 MiniTabAnimation(TabStripGtk* tabstrip, int index)
548 : TabAnimation(tabstrip, MINI),
549 index_(index) {
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())
554 start_mini_count--;
555 else
556 start_mini_count++;
557 tabstrip_->GetTabAt(index)->set_animating_mini_change(true);
558 GenerateStartAndEndWidths(tab_count, tab_count, start_mini_count,
559 end_mini_count);
562 protected:
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_) {
572 if (tab->mini()) {
573 return animation_.CurrentValueBetween(
574 start_selected_width_,
575 static_cast<double>(TabGtk::GetMiniWidth()));
576 } else {
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_);
594 private:
595 // Index of the tab whose mini-state changed.
596 int index_;
598 DISALLOW_COPY_AND_ASSIGN(MiniTabAnimation);
601 ////////////////////////////////////////////////////////////////////////////////
603 // Handles the animation when a tabs mini-state changes and the tab moves as a
604 // result.
605 class MiniMoveAnimation : public TabStripGtk::TabAnimation {
606 public:
607 MiniMoveAnimation(TabStripGtk* tabstrip,
608 int from_index,
609 int to_index,
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())
620 start_mini_count--;
621 else
622 start_mini_count++;
623 GenerateStartAndEndWidths(tab_count, tab_count, start_mini_count,
624 end_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(),
636 target_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);
660 } else {
661 // The tab was was made a normal tab.
662 if (index == from_index_) {
663 return animation_.CurrentValueBetween(
664 TabGtk::GetMiniWidth() + kTabHOffset, 0);
667 return 0;
670 protected:
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());
682 if (tab->mini())
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_);
694 private:
695 // The tab being moved.
696 TabGtk* tab_;
698 // Initial bounds of tab_.
699 gfx::Rect start_bounds_;
701 // Target bounds.
702 gfx::Rect target_bounds_;
704 // Start and end indices of the tab.
705 int from_index_;
706 int to_index_;
708 DISALLOW_COPY_AND_ASSIGN(MiniMoveAnimation);
711 ////////////////////////////////////////////////////////////////////////////////
712 // TabStripGtk, public:
714 // static
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),
723 model_(model),
724 window_(window),
725 theme_service_(GtkThemeService::GetFrom(model->profile())),
726 added_as_message_loop_observer_(false),
727 hover_tab_selector_(model),
728 weak_factory_(this),
729 layout_factory_(this) {
732 TabStripGtk::~TabStripGtk() {
733 model_->RemoveObserver(this);
734 tabstrip_.Destroy();
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;
744 tab_data_.clear();
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,
762 NULL, 0,
763 static_cast<GdkDragAction>(
764 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
765 static const int targets[] = { ui::TEXT_URI_LIST,
766 ui::NETSCAPE_URL,
767 ui::TEXT_PLAIN,
768 -1 };
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);
819 if (tab->dragging())
820 return true;
822 return false;
825 void TabStripGtk::Layout() {
826 // Called from:
827 // - window resize
828 // - animation completion
829 StopAnimation();
831 GenerateIdealBounds();
832 int tab_count = GetTabCount();
833 int tab_right = 0;
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) {
852 bounds_ = 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()) {
859 --index;
860 } else {
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;
867 } else {
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.
897 StopAnimation();
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
901 // child view list.
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!";
907 tab_data_.erase(it);
908 break;
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.
920 Layout();
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;
930 Layout();
933 gfx::Point TabStripGtk::GetTabStripOriginForWidget(GtkWidget* target) {
934 int x, y;
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
940 // toplevel instead.
941 if (!gtk_widget_translate_coordinates(
942 gtk_widget_get_toplevel(widget()), target, 0, 0, &x, &y)) {
943 NOTREACHED();
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();
966 } else {
967 return NULL;
972 return NULL;
975 ////////////////////////////////////////////////////////////////////////////////
976 // TabStripGtk, TabStripModelObserver implementation:
978 void TabStripGtk::TabInsertedAt(WebContents* contents,
979 int index,
980 bool foreground) {
981 TRACE_EVENT0("ui::gtk", "TabStripGtk::TabInsertedAt");
983 DCHECK(contents);
984 DCHECK(index == TabStripModel::kNoTab || model_->ContainsIndex(index));
986 StopAnimation();
988 bool contains_tab = false;
989 TabGtk* tab = NULL;
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
993 // again.
994 if (IsDragSessionActive()) {
995 tab = drag_controller_->GetDraggedTabForContents(contents);
996 if (tab) {
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;
1012 if (!tab)
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);
1035 } else {
1036 Layout();
1039 ReStack();
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
1047 // model index.
1048 GetTabAt(index)->set_closing(true);
1051 void TabStripGtk::ActiveTabChanged(WebContents* old_contents,
1052 WebContents* new_contents,
1053 int index,
1054 int reason) {
1055 TRACE_EVENT0("ui::gtk", "TabStripGtk::ActiveTabChanged");
1056 ReStack();
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))
1065 Layout();
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(),
1083 it1);
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,
1103 int from_index,
1104 int to_index) {
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);
1114 ReStack();
1117 void TabStripGtk::TabChangedAt(WebContents* contents,
1118 int index,
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.
1127 return;
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,
1138 int index) {
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))
1145 return;
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);
1153 } else {
1154 Layout();
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 {
1166 if (tab->closing())
1167 return false;
1169 return GetIndexOfTab(tab) == model_->active_index();
1172 bool TabStripGtk::IsTabSelected(const TabGtk* tab) const {
1173 if (tab->closing())
1174 return false;
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);
1182 return false;
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 {
1192 if (tab->closing())
1193 return false;
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
1221 // the TabStrip).
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);
1239 return false;
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) {
1253 NOTIMPLEMENTED();
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.
1268 NOTIMPLEMENTED();
1271 void TabStripGtk::MaybeStartDrag(TabGtk* tab, const gfx::Point& point) {
1272 // Don't accidentally start any drag operations during animations if the
1273 // mouse is down.
1274 if (IsAnimating() || tab->closing() || !HasAvailableDragActions())
1275 return;
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(
1311 TabGtk* tab) {
1312 return new TabStripMenuController(tab, model(), GetIndexOfTab(tab));
1315 ///////////////////////////////////////////////////////////////////////////////
1316 // TabStripGtk, MessageLoop::Observer implementation:
1318 void TabStripGtk::WillProcessEvent(GdkEvent* event) {
1319 // Nothing to do.
1322 void TabStripGtk::DidProcessEvent(GdkEvent* event) {
1323 switch (event->type) {
1324 case GDK_MOTION_NOTIFY:
1325 case GDK_LEAVE_NOTIFY:
1326 HandleGlobalMouseMoveEvent();
1327 break;
1328 default:
1329 break;
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 {
1351 int mini_count = 0;
1352 for (size_t i = 0; i < tab_data_.size(); ++i) {
1353 if (tab_data_[i].tab->mini())
1354 mini_count++;
1355 else
1356 return mini_count;
1358 return mini_count;
1361 int TabStripGtk::GetAvailableWidthForTabs(TabGtk* last_tab) const {
1362 if (!base::i18n::IsRTL())
1363 return last_tab->x() - bounds_.x() + last_tab->width();
1364 else
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()) {
1372 --index;
1373 } else if (current_tab == tab) {
1374 return index;
1377 return -1;
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 &&
1389 index >=
1390 static_cast<RemoveTabAnimation*>(active_animation_.get())->index()) {
1391 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());
1404 delete removed;
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(
1414 FROM_HERE,
1415 base::Bind(&TabStripGtk::ResizeLayoutTabs,
1416 weak_factory_.GetWeakPtr()),
1417 base::TimeDelta::FromMilliseconds(kResizeTabsTimeMs));
1419 } else {
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
1422 // back over it.
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;
1442 if (tab->mini())
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,
1449 tab_height);
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;
1459 if (toplevel) {
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);
1475 } else {
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(),
1483 bounds.height());
1486 void TabStripGtk::GetDesiredTabWidths(int tab_count,
1487 int mini_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.
1501 return;
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();
1510 available_width -=
1511 (kNewTabButtonHOffset + newtab_button_->WidgetAllocation().width);
1512 } else {
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();
1530 return;
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
1556 // of 10.
1557 if (tab_count > 1) {
1558 if ((min_unselected_width < min_selected_width) &&
1559 (desired_tab_width < min_selected_width)) {
1560 double calc_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;
1578 return kTabHOffset;
1581 int TabStripGtk::tab_start_x() const {
1582 return 0;
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.
1598 return;
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
1606 // size.
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);
1621 gint x, y;
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.
1634 return;
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())
1641 active_tab = tab;
1642 else
1643 tab->Raise();
1645 if (active_tab)
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,
1664 bool drop_before,
1665 bool* is_beneath) {
1666 DCHECK_NE(drop_index, -1);
1667 int center_x;
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.
1672 if (drop_before)
1673 center_x = bounds.x() - (kTabHOffset / 2);
1674 else
1675 center_x = bounds.x() + (bounds.width() / 2);
1676 } else {
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.
1693 *is_beneath = true;
1694 if (*is_beneath)
1695 drop_bounds.Offset(0, drop_bounds.height() + bounds().height());
1697 return drop_bounds;
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);
1716 else
1717 SetDropIndex(i, false);
1718 return;
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) {
1727 bool is_beneath;
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);
1734 else
1735 hover_tab_selector_.CancelTabTransition();
1737 if (!drop_info_.get()) {
1738 drop_info_.reset(new DropInfo(index, drop_before, !is_beneath));
1739 } else {
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) {
1744 return;
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())
1763 return false;
1765 const int drop_index = drop_info_->drop_index;
1766 const bool drop_before = drop_info_->drop_before;
1768 // Destroy the drop indicator.
1769 drop_info_.reset();
1771 // Cancel any pending tab transition.
1772 hover_tab_selector_.CancelTabTransition();
1774 GURL url;
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;
1781 } else {
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())
1786 return false;
1788 chrome::NavigateParams params(window()->browser(), url,
1789 content::PAGE_TRANSITION_LINK);
1790 params.tabstrip_index = drop_index;
1792 if (drop_before) {
1793 params.disposition = NEW_FOREGROUND_TAB;
1794 } else {
1795 params.disposition = CURRENT_TAB;
1796 params.source_contents = model_->GetWebContentsAt(drop_index);
1799 chrome::Navigate(&params);
1801 return true;
1804 // static
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,
1813 bool point_down)
1814 : drop_index(drop_index),
1815 drop_before(drop_before),
1816 point_down(point_down) {
1817 CreateContainer();
1818 drop_arrow = GetDropArrowImage(point_down);
1821 TabStripGtk::DropInfo::~DropInfo() {
1822 DestroyContainer();
1825 gboolean TabStripGtk::DropInfo::OnExposeEvent(GtkWidget* widget,
1826 GdkEventExpose* event) {
1827 TRACE_EVENT0("ui::gtk", "TabStripGtk::DropInfo::OnExposeEvent");
1829 if (ui::IsScreenComposited()) {
1830 SetContainerTransparency();
1831 } else {
1832 SetContainerShapeMask();
1835 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
1836 gdk_cairo_rectangle(cr, &event->area);
1837 cairo_clip(cr);
1839 drop_arrow->ToCairo()->SetSource(cr, widget, 0, 0);
1840 cairo_paint(cr);
1842 cairo_destroy(cr);
1844 return FALSE;
1847 // Sets the color map of the container window to allow the window to be
1848 // transparent.
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.
1854 if (!colormap)
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));
1864 if (!cairo_context)
1865 return;
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
1880 // screen.
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();
1929 // Called from:
1930 // - animation tick
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();
1936 if (animation)
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,
1943 tab_height);
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;
1953 StopAnimation();
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) {
1974 StopAnimation();
1975 active_animation_.reset(new MoveTabAnimation(this, from_index, to_index));
1976 active_animation_->Start();
1979 void TabStripGtk::StartResizeLayoutAnimation() {
1980 StopAnimation();
1981 active_animation_.reset(new ResizeLayoutAnimation(this));
1982 active_animation_->Start();
1985 void TabStripGtk::StartMiniTabAnimation(int index) {
1986 StopAnimation();
1987 active_animation_.reset(new MiniTabAnimation(this, index));
1988 active_animation_->Start();
1991 void TabStripGtk::StartMiniMoveTabAnimation(int from_index,
1992 int to_index,
1993 const gfx::Rect& start_bounds) {
1994 StopAnimation();
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,
2001 bool layout) {
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);
2008 if (layout)
2009 Layout();
2012 void TabStripGtk::OnMap(GtkWidget* widget) {
2013 ReStack();
2016 gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event) {
2017 TRACE_EVENT0("ui::gtk", "TabStripGtk::OnExpose");
2019 if (gdk_region_empty(event->region))
2020 return TRUE;
2022 // If we're only repainting favicons, optimize the paint path and only draw
2023 // the favicons.
2024 GdkRectangle* rects;
2025 gint num_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);
2032 g_free(rects);
2033 return TRUE;
2035 g_free(rects);
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
2050 // tabstrip.
2051 event->area.width = bounds_.width();
2052 } else {
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
2055 // rectangle given.
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;
2063 event->area.x = 0;
2064 event->area.y = 0;
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);
2083 } else {
2084 selected_tab = tab;
2088 // Paint the selected tab last, so it overlaps all the others.
2089 if (selected_tab) {
2090 gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()),
2091 selected_tab->widget(), event);
2094 return TRUE;
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)
2106 return;
2108 SetBounds(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)
2113 return;
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) {
2119 Layout();
2120 } else if (!layout_factory_.HasWeakPtrs()) {
2121 base::MessageLoop::current()->PostDelayedTask(
2122 FROM_HERE,
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);
2131 return TRUE;
2134 gboolean TabStripGtk::OnDragDrop(GtkWidget* widget, GdkDragContext* context,
2135 gint x, gint y, guint time) {
2136 if (!drop_info_.get())
2137 return FALSE;
2139 GdkAtom target = gtk_drag_dest_find_target(widget, context, NULL);
2140 if (target != GDK_NONE)
2141 gtk_drag_finish(context, FALSE, FALSE, time);
2142 else
2143 gtk_drag_get_data(widget, context, target, time);
2145 return TRUE;
2148 gboolean TabStripGtk::OnDragLeave(GtkWidget* widget, GdkDragContext* context,
2149 guint time) {
2150 // Destroy the drop indicator.
2151 drop_info_->DestroyContainer();
2153 // Cancel any pending tab transition.
2154 hover_tab_selector_.CancelTabTransition();
2156 return FALSE;
2159 gboolean TabStripGtk::OnDragDataReceived(GtkWidget* widget,
2160 GdkDragContext* context,
2161 gint x, gint y,
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);
2174 return TRUE;
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) {
2184 case 1:
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);
2189 break;
2190 case 2: {
2191 // On middle-click, try to parse the PRIMARY selection as a URL and load
2192 // it instead of creating a blank page.
2193 GURL url;
2194 if (!gtk_util::URLFromPrimarySelection(model_->profile(), &url))
2195 return;
2197 Browser* browser = window_->browser();
2198 DCHECK(browser);
2199 chrome::AddSelectedTabWithURL(
2200 browser, url, content::PAGE_TRANSITION_TYPED);
2201 break;
2203 default:
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(),
2214 bds.x(), bds.y());
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.
2221 int t = 0;
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);
2228 ++t;
2229 break;
2231 ++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) {
2241 cairo_save(cr);
2242 GetTabAt(tabs_to_paint[i])->PaintFaviconArea(tabstrip_.get(), cr);
2243 cairo_restore(cr);
2246 cairo_destroy(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);
2263 return button;
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);