• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
6 
7 #include <algorithm>
8 
9 #include "base/i18n/rtl.h"
10 #include "base/string_util.h"
11 #include "base/utf_string_conversions.h"
12 #include "chrome/browser/autocomplete/autocomplete.h"
13 #include "chrome/browser/autocomplete/autocomplete_classifier.h"
14 #include "chrome/browser/autocomplete/autocomplete_match.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/tabs/tab_strip_model_delegate.h"
17 #include "chrome/browser/themes/theme_service.h"
18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/browser_navigator.h"
20 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
21 #include "chrome/browser/ui/gtk/custom_button.h"
22 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
23 #include "chrome/browser/ui/gtk/gtk_util.h"
24 #include "chrome/browser/ui/gtk/tabs/dragged_tab_controller_gtk.h"
25 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
26 #include "content/browser/tab_contents/tab_contents.h"
27 #include "content/common/notification_service.h"
28 #include "content/common/notification_type.h"
29 #include "grit/app_resources.h"
30 #include "grit/theme_resources.h"
31 #include "ui/base/animation/animation_delegate.h"
32 #include "ui/base/animation/slide_animation.h"
33 #include "ui/base/dragdrop/gtk_dnd_util.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/gfx/gtk_util.h"
36 #include "ui/gfx/point.h"
37 
38 namespace {
39 
40 const int kDefaultAnimationDurationMs = 100;
41 const int kResizeLayoutAnimationDurationMs = 166;
42 const int kReorderAnimationDurationMs = 166;
43 const int kAnimateToBoundsDurationMs = 150;
44 const int kMiniTabAnimationDurationMs = 150;
45 
46 const int kNewTabButtonHOffset = -5;
47 const int kNewTabButtonVOffset = 5;
48 
49 // The delay between when the mouse leaves the tabstrip and the resize animation
50 // is started.
51 const int kResizeTabsTimeMs = 300;
52 
53 // The range outside of the tabstrip where the pointer must enter/leave to
54 // start/stop the resize animation.
55 const int kTabStripAnimationVSlop = 40;
56 
57 const int kHorizontalMoveThreshold = 16;  // pixels
58 
59 // The horizontal offset from one tab to the next, which results in overlapping
60 // tabs.
61 const int kTabHOffset = -16;
62 
63 // A linux specific menu item for toggling window decorations.
64 const int kShowWindowDecorationsCommand = 200;
65 
66 // Size of the drop indicator.
67 static int drop_indicator_width;
68 static int drop_indicator_height;
69 
Round(double x)70 inline int Round(double x) {
71   return static_cast<int>(x + 0.5);
72 }
73 
74 // widget->allocation is not guaranteed to be set.  After window creation,
75 // we pick up the normal bounds by connecting to the configure-event signal.
GetInitialWidgetBounds(GtkWidget * widget)76 gfx::Rect GetInitialWidgetBounds(GtkWidget* widget) {
77   GtkRequisition request;
78   gtk_widget_size_request(widget, &request);
79   return gfx::Rect(0, 0, request.width, request.height);
80 }
81 
82 // Sort rectangles based on their x position.  We don't care about y position
83 // so we don't bother breaking ties.
CompareGdkRectangles(const void * p1,const void * p2)84 int CompareGdkRectangles(const void* p1, const void* p2) {
85   int p1_x = static_cast<const GdkRectangle*>(p1)->x;
86   int p2_x = static_cast<const GdkRectangle*>(p2)->x;
87   if (p1_x < p2_x)
88     return -1;
89   else if (p1_x == p2_x)
90     return 0;
91   return 1;
92 }
93 
GdkRectMatchesTabFaviconBounds(const GdkRectangle & gdk_rect,TabGtk * tab)94 bool GdkRectMatchesTabFaviconBounds(const GdkRectangle& gdk_rect, TabGtk* tab) {
95   gfx::Rect favicon_bounds = tab->favicon_bounds();
96   return gdk_rect.x == favicon_bounds.x() + tab->x() &&
97       gdk_rect.y == favicon_bounds.y() + tab->y() &&
98       gdk_rect.width == favicon_bounds.width() &&
99       gdk_rect.height == favicon_bounds.height();
100 }
101 
102 }  // namespace
103 
104 ////////////////////////////////////////////////////////////////////////////////
105 //
106 // TabAnimation
107 //
108 //  A base class for all TabStrip animations.
109 //
110 class TabStripGtk::TabAnimation : public ui::AnimationDelegate {
111  public:
112   friend class TabStripGtk;
113 
114   // Possible types of animation.
115   enum Type {
116     INSERT,
117     REMOVE,
118     MOVE,
119     RESIZE,
120     MINI,
121     MINI_MOVE
122   };
123 
TabAnimation(TabStripGtk * tabstrip,Type type)124   TabAnimation(TabStripGtk* tabstrip, Type type)
125       : tabstrip_(tabstrip),
126         animation_(this),
127         start_selected_width_(0),
128         start_unselected_width_(0),
129         end_selected_width_(0),
130         end_unselected_width_(0),
131         layout_on_completion_(false),
132         type_(type) {
133   }
~TabAnimation()134   virtual ~TabAnimation() {}
135 
type() const136   Type type() const { return type_; }
137 
Start()138   void Start() {
139     animation_.SetSlideDuration(GetDuration());
140     animation_.SetTweenType(ui::Tween::EASE_OUT);
141     if (!animation_.IsShowing()) {
142       animation_.Reset();
143       animation_.Show();
144     }
145   }
146 
Stop()147   void Stop() {
148     animation_.Stop();
149   }
150 
set_layout_on_completion(bool layout_on_completion)151   void set_layout_on_completion(bool layout_on_completion) {
152     layout_on_completion_ = layout_on_completion;
153   }
154 
155   // Retrieves the width for the Tab at the specified index if an animation is
156   // active.
GetCurrentTabWidth(TabStripGtk * tabstrip,TabStripGtk::TabAnimation * animation,int index)157   static double GetCurrentTabWidth(TabStripGtk* tabstrip,
158                                    TabStripGtk::TabAnimation* animation,
159                                    int index) {
160     TabGtk* tab = tabstrip->GetTabAt(index);
161     double tab_width;
162     if (tab->mini()) {
163       tab_width = TabGtk::GetMiniWidth();
164     } else {
165       double unselected, selected;
166       tabstrip->GetCurrentTabWidths(&unselected, &selected);
167       tab_width = tab->IsSelected() ? selected : unselected;
168     }
169 
170     if (animation) {
171       double specified_tab_width = animation->GetWidthForTab(index);
172       if (specified_tab_width != -1)
173         tab_width = specified_tab_width;
174     }
175 
176     return tab_width;
177   }
178 
179   // Overridden from ui::AnimationDelegate:
AnimationProgressed(const ui::Animation * animation)180   virtual void AnimationProgressed(const ui::Animation* animation) {
181     tabstrip_->AnimationLayout(end_unselected_width_);
182   }
183 
AnimationEnded(const ui::Animation * animation)184   virtual void AnimationEnded(const ui::Animation* animation) {
185     tabstrip_->FinishAnimation(this, layout_on_completion_);
186     // This object is destroyed now, so we can't do anything else after this.
187   }
188 
AnimationCanceled(const ui::Animation * animation)189   virtual void AnimationCanceled(const ui::Animation* animation) {
190     AnimationEnded(animation);
191   }
192 
193   // Returns the gap before the tab at the specified index. Subclass if during
194   // an animation you need to insert a gap before a tab.
GetGapWidth(int index)195   virtual double GetGapWidth(int index) {
196     return 0;
197   }
198 
199  protected:
200   // Returns the duration of the animation.
GetDuration() const201   virtual int GetDuration() const {
202     return kDefaultAnimationDurationMs;
203   }
204 
205   // Subclasses override to return the width of the Tab at the specified index
206   // at the current animation frame. -1 indicates the default width should be
207   // used for the Tab.
GetWidthForTab(int index) const208   virtual double GetWidthForTab(int index) const {
209     return -1;  // Use default.
210   }
211 
212   // Figure out the desired start and end widths for the specified pre- and
213   // post- animation tab counts.
GenerateStartAndEndWidths(int start_tab_count,int end_tab_count,int start_mini_count,int end_mini_count)214   void GenerateStartAndEndWidths(int start_tab_count, int end_tab_count,
215                                  int start_mini_count,
216                                  int end_mini_count) {
217     tabstrip_->GetDesiredTabWidths(start_tab_count, start_mini_count,
218                                    &start_unselected_width_,
219                                    &start_selected_width_);
220     double standard_tab_width =
221         static_cast<double>(TabRendererGtk::GetStandardSize().width());
222 
223     if ((end_tab_count - start_tab_count) > 0 &&
224         start_unselected_width_ < standard_tab_width) {
225       double minimum_tab_width = static_cast<double>(
226           TabRendererGtk::GetMinimumUnselectedSize().width());
227       start_unselected_width_ -= minimum_tab_width / start_tab_count;
228     }
229 
230     tabstrip_->GenerateIdealBounds();
231     tabstrip_->GetDesiredTabWidths(end_tab_count, end_mini_count,
232                                    &end_unselected_width_,
233                                    &end_selected_width_);
234   }
235 
236   TabStripGtk* tabstrip_;
237   ui::SlideAnimation animation_;
238 
239   double start_selected_width_;
240   double start_unselected_width_;
241   double end_selected_width_;
242   double end_unselected_width_;
243 
244  private:
245   // True if a complete re-layout is required upon completion of the animation.
246   // Subclasses set this if they don't perform a complete layout
247   // themselves and canceling the animation may leave the strip in an
248   // inconsistent state.
249   bool layout_on_completion_;
250 
251   const Type type_;
252 
253   DISALLOW_COPY_AND_ASSIGN(TabAnimation);
254 };
255 
256 ////////////////////////////////////////////////////////////////////////////////
257 
258 // Handles insertion of a Tab at |index|.
259 class InsertTabAnimation : public TabStripGtk::TabAnimation {
260  public:
InsertTabAnimation(TabStripGtk * tabstrip,int index)261   explicit InsertTabAnimation(TabStripGtk* tabstrip, int index)
262       : TabAnimation(tabstrip, INSERT),
263         index_(index) {
264     int tab_count = tabstrip->GetTabCount();
265     int end_mini_count = tabstrip->GetMiniTabCount();
266     int start_mini_count = end_mini_count;
267     if (index < end_mini_count)
268       start_mini_count--;
269     GenerateStartAndEndWidths(tab_count - 1, tab_count, start_mini_count,
270                               end_mini_count);
271   }
~InsertTabAnimation()272   virtual ~InsertTabAnimation() {}
273 
274  protected:
275   // Overridden from TabStripGtk::TabAnimation:
GetWidthForTab(int index) const276   virtual double GetWidthForTab(int index) const {
277     if (index == index_) {
278       bool is_selected = tabstrip_->model()->active_index() == index;
279       double start_width, target_width;
280       if (index < tabstrip_->GetMiniTabCount()) {
281         start_width = TabGtk::GetMinimumSelectedSize().width();
282         target_width = TabGtk::GetMiniWidth();
283       } else {
284         target_width =
285             is_selected ? end_unselected_width_ : end_selected_width_;
286         start_width =
287             is_selected ? TabGtk::GetMinimumSelectedSize().width() :
288                           TabGtk::GetMinimumUnselectedSize().width();
289       }
290 
291       double delta = target_width - start_width;
292       if (delta > 0)
293         return start_width + (delta * animation_.GetCurrentValue());
294 
295       return start_width;
296     }
297 
298     if (tabstrip_->GetTabAt(index)->mini())
299       return TabGtk::GetMiniWidth();
300 
301     if (tabstrip_->GetTabAt(index)->IsSelected()) {
302       double delta = end_selected_width_ - start_selected_width_;
303       return start_selected_width_ + (delta * animation_.GetCurrentValue());
304     }
305 
306     double delta = end_unselected_width_ - start_unselected_width_;
307     return start_unselected_width_ + (delta * animation_.GetCurrentValue());
308   }
309 
310  private:
311   int index_;
312 
313   DISALLOW_COPY_AND_ASSIGN(InsertTabAnimation);
314 };
315 
316 ////////////////////////////////////////////////////////////////////////////////
317 
318 // Handles removal of a Tab from |index|
319 class RemoveTabAnimation : public TabStripGtk::TabAnimation {
320  public:
RemoveTabAnimation(TabStripGtk * tabstrip,int index,TabContents * contents)321   RemoveTabAnimation(TabStripGtk* tabstrip, int index, TabContents* contents)
322       : TabAnimation(tabstrip, REMOVE),
323         index_(index) {
324     int tab_count = tabstrip->GetTabCount();
325     int start_mini_count = tabstrip->GetMiniTabCount();
326     int end_mini_count = start_mini_count;
327     if (index < start_mini_count)
328       end_mini_count--;
329     GenerateStartAndEndWidths(tab_count, tab_count - 1, start_mini_count,
330                               end_mini_count);
331     // If the last non-mini-tab is being removed we force a layout on
332     // completion. This is necessary as the value returned by GetTabHOffset
333     // changes once the tab is actually removed (which happens at the end of
334     // the animation), and unless we layout GetTabHOffset won't be called after
335     // the removal.
336     // We do the same when the last mini-tab is being removed for the same
337     // reason.
338     set_layout_on_completion(start_mini_count > 0 &&
339                              (end_mini_count == 0 ||
340                               (start_mini_count == end_mini_count &&
341                                tab_count == start_mini_count + 1)));
342   }
343 
~RemoveTabAnimation()344   virtual ~RemoveTabAnimation() {}
345 
346   // Returns the index of the tab being removed.
index() const347   int index() const { return index_; }
348 
349  protected:
350   // Overridden from TabStripGtk::TabAnimation:
GetWidthForTab(int index) const351   virtual double GetWidthForTab(int index) const {
352     TabGtk* tab = tabstrip_->GetTabAt(index);
353 
354     if (index == index_) {
355       // The tab(s) being removed are gradually shrunken depending on the state
356       // of the animation.
357       if (tab->mini()) {
358         return animation_.CurrentValueBetween(TabGtk::GetMiniWidth(),
359                                               -kTabHOffset);
360       }
361 
362       // Removed animated Tabs are never selected.
363       double start_width = start_unselected_width_;
364       // Make sure target_width is at least abs(kTabHOffset), otherwise if
365       // less than kTabHOffset during layout tabs get negatively offset.
366       double target_width =
367           std::max(abs(kTabHOffset),
368                    TabGtk::GetMinimumUnselectedSize().width() + kTabHOffset);
369       return animation_.CurrentValueBetween(start_width, target_width);
370     }
371 
372     if (tab->mini())
373       return TabGtk::GetMiniWidth();
374 
375     if (tabstrip_->available_width_for_tabs_ != -1 &&
376         index_ != tabstrip_->GetTabCount() - 1) {
377       return TabStripGtk::TabAnimation::GetWidthForTab(index);
378     }
379 
380     // All other tabs are sized according to the start/end widths specified at
381     // the start of the animation.
382     if (tab->IsSelected()) {
383       double delta = end_selected_width_ - start_selected_width_;
384       return start_selected_width_ + (delta * animation_.GetCurrentValue());
385     }
386 
387     double delta = end_unselected_width_ - start_unselected_width_;
388     return start_unselected_width_ + (delta * animation_.GetCurrentValue());
389   }
390 
AnimationEnded(const ui::Animation * animation)391   virtual void AnimationEnded(const ui::Animation* animation) {
392     tabstrip_->RemoveTabAt(index_);
393     TabStripGtk::TabAnimation::AnimationEnded(animation);
394   }
395 
396  private:
397   int index_;
398 
399   DISALLOW_COPY_AND_ASSIGN(RemoveTabAnimation);
400 };
401 
402 ////////////////////////////////////////////////////////////////////////////////
403 
404 // Handles the movement of a Tab from one position to another.
405 class MoveTabAnimation : public TabStripGtk::TabAnimation {
406  public:
MoveTabAnimation(TabStripGtk * tabstrip,int tab_a_index,int tab_b_index)407   MoveTabAnimation(TabStripGtk* tabstrip, int tab_a_index, int tab_b_index)
408       : TabAnimation(tabstrip, MOVE),
409         start_tab_a_bounds_(tabstrip_->GetIdealBounds(tab_b_index)),
410         start_tab_b_bounds_(tabstrip_->GetIdealBounds(tab_a_index)) {
411     tab_a_ = tabstrip_->GetTabAt(tab_a_index);
412     tab_b_ = tabstrip_->GetTabAt(tab_b_index);
413 
414     // Since we don't do a full TabStrip re-layout, we need to force a full
415     // layout upon completion since we're not guaranteed to be in a good state
416     // if for example the animation is canceled.
417     set_layout_on_completion(true);
418   }
~MoveTabAnimation()419   virtual ~MoveTabAnimation() {}
420 
421   // Overridden from ui::AnimationDelegate:
AnimationProgressed(const ui::Animation * animation)422   virtual void AnimationProgressed(const ui::Animation* animation) {
423     // Position Tab A
424     double distance = start_tab_b_bounds_.x() - start_tab_a_bounds_.x();
425     double delta = distance * animation_.GetCurrentValue();
426     double new_x = start_tab_a_bounds_.x() + delta;
427     gfx::Rect bounds(Round(new_x), start_tab_a_bounds_.y(), tab_a_->width(),
428         tab_a_->height());
429     tabstrip_->SetTabBounds(tab_a_, bounds);
430 
431     // Position Tab B
432     distance = start_tab_a_bounds_.x() - start_tab_b_bounds_.x();
433     delta = distance * animation_.GetCurrentValue();
434     new_x = start_tab_b_bounds_.x() + delta;
435     bounds = gfx::Rect(Round(new_x), start_tab_b_bounds_.y(), tab_b_->width(),
436         tab_b_->height());
437     tabstrip_->SetTabBounds(tab_b_, bounds);
438   }
439 
440  protected:
441   // Overridden from TabStripGtk::TabAnimation:
GetDuration() const442   virtual int GetDuration() const { return kReorderAnimationDurationMs; }
443 
444  private:
445   // The two tabs being exchanged.
446   TabGtk* tab_a_;
447   TabGtk* tab_b_;
448 
449   // ...and their bounds.
450   gfx::Rect start_tab_a_bounds_;
451   gfx::Rect start_tab_b_bounds_;
452 
453   DISALLOW_COPY_AND_ASSIGN(MoveTabAnimation);
454 };
455 
456 ////////////////////////////////////////////////////////////////////////////////
457 
458 // Handles the animated resize layout of the entire TabStrip from one width
459 // to another.
460 class ResizeLayoutAnimation : public TabStripGtk::TabAnimation {
461  public:
ResizeLayoutAnimation(TabStripGtk * tabstrip)462   explicit ResizeLayoutAnimation(TabStripGtk* tabstrip)
463       : TabAnimation(tabstrip, RESIZE) {
464     int tab_count = tabstrip->GetTabCount();
465     int mini_tab_count = tabstrip->GetMiniTabCount();
466     GenerateStartAndEndWidths(tab_count, tab_count, mini_tab_count,
467                               mini_tab_count);
468     InitStartState();
469   }
~ResizeLayoutAnimation()470   virtual ~ResizeLayoutAnimation() {}
471 
472   // Overridden from ui::AnimationDelegate:
AnimationEnded(const ui::Animation * animation)473   virtual void AnimationEnded(const ui::Animation* animation) {
474     tabstrip_->needs_resize_layout_ = false;
475     TabStripGtk::TabAnimation::AnimationEnded(animation);
476   }
477 
478  protected:
479   // Overridden from TabStripGtk::TabAnimation:
GetDuration() const480   virtual int GetDuration() const {
481     return kResizeLayoutAnimationDurationMs;
482   }
483 
GetWidthForTab(int index) const484   virtual double GetWidthForTab(int index) const {
485     TabGtk* tab = tabstrip_->GetTabAt(index);
486 
487     if (tab->mini())
488       return TabGtk::GetMiniWidth();
489 
490     if (tab->IsSelected()) {
491       return animation_.CurrentValueBetween(start_selected_width_,
492                                             end_selected_width_);
493     }
494 
495     return animation_.CurrentValueBetween(start_unselected_width_,
496                                           end_unselected_width_);
497   }
498 
499  private:
500   // We need to start from the current widths of the Tabs as they were last
501   // laid out, _not_ the last known good state, which is what'll be done if we
502   // don't measure the Tab sizes here and just go with the default TabAnimation
503   // behavior...
InitStartState()504   void InitStartState() {
505     for (int i = 0; i < tabstrip_->GetTabCount(); ++i) {
506       TabGtk* current_tab = tabstrip_->GetTabAt(i);
507       if (!current_tab->mini()) {
508         if (current_tab->IsSelected()) {
509           start_selected_width_ = current_tab->width();
510         } else {
511           start_unselected_width_ = current_tab->width();
512         }
513       }
514     }
515   }
516 
517   DISALLOW_COPY_AND_ASSIGN(ResizeLayoutAnimation);
518 };
519 
520 // Handles a tabs mini-state changing while the tab does not change position
521 // in the model.
522 class MiniTabAnimation : public TabStripGtk::TabAnimation {
523  public:
MiniTabAnimation(TabStripGtk * tabstrip,int index)524   explicit MiniTabAnimation(TabStripGtk* tabstrip, int index)
525       : TabAnimation(tabstrip, MINI),
526         index_(index) {
527     int tab_count = tabstrip->GetTabCount();
528     int start_mini_count = tabstrip->GetMiniTabCount();
529     int end_mini_count = start_mini_count;
530     if (tabstrip->GetTabAt(index)->mini())
531       start_mini_count--;
532     else
533       start_mini_count++;
534     tabstrip_->GetTabAt(index)->set_animating_mini_change(true);
535     GenerateStartAndEndWidths(tab_count, tab_count, start_mini_count,
536                               end_mini_count);
537   }
538 
539  protected:
540   // Overridden from TabStripGtk::TabAnimation:
GetDuration() const541   virtual int GetDuration() const {
542     return kMiniTabAnimationDurationMs;
543   }
544 
GetWidthForTab(int index) const545   virtual double GetWidthForTab(int index) const {
546     TabGtk* tab = tabstrip_->GetTabAt(index);
547 
548     if (index == index_) {
549       if (tab->mini()) {
550         return animation_.CurrentValueBetween(
551             start_selected_width_,
552             static_cast<double>(TabGtk::GetMiniWidth()));
553       } else {
554         return animation_.CurrentValueBetween(
555             static_cast<double>(TabGtk::GetMiniWidth()),
556             end_selected_width_);
557       }
558     } else if (tab->mini()) {
559       return TabGtk::GetMiniWidth();
560     }
561 
562     if (tab->IsSelected()) {
563       return animation_.CurrentValueBetween(start_selected_width_,
564                                             end_selected_width_);
565     }
566 
567     return animation_.CurrentValueBetween(start_unselected_width_,
568                                           end_unselected_width_);
569   }
570 
571  private:
572   // Index of the tab whose mini-state changed.
573   int index_;
574 
575   DISALLOW_COPY_AND_ASSIGN(MiniTabAnimation);
576 };
577 
578 ////////////////////////////////////////////////////////////////////////////////
579 
580 // Handles the animation when a tabs mini-state changes and the tab moves as a
581 // result.
582 class MiniMoveAnimation : public TabStripGtk::TabAnimation {
583  public:
MiniMoveAnimation(TabStripGtk * tabstrip,int from_index,int to_index,const gfx::Rect & start_bounds)584   explicit MiniMoveAnimation(TabStripGtk* tabstrip,
585                              int from_index,
586                              int to_index,
587                              const gfx::Rect& start_bounds)
588       : TabAnimation(tabstrip, MINI_MOVE),
589         tab_(tabstrip->GetTabAt(to_index)),
590         start_bounds_(start_bounds),
591         from_index_(from_index),
592         to_index_(to_index) {
593     int tab_count = tabstrip->GetTabCount();
594     int start_mini_count = tabstrip->GetMiniTabCount();
595     int end_mini_count = start_mini_count;
596     if (tabstrip->GetTabAt(to_index)->mini())
597       start_mini_count--;
598     else
599       start_mini_count++;
600     GenerateStartAndEndWidths(tab_count, tab_count, start_mini_count,
601                               end_mini_count);
602     target_bounds_ = tabstrip->GetIdealBounds(to_index);
603     tab_->set_animating_mini_change(true);
604   }
605 
606   // Overridden from ui::AnimationDelegate:
AnimationProgressed(const ui::Animation * animation)607   virtual void AnimationProgressed(const ui::Animation* animation) {
608     // Do the normal layout.
609     TabAnimation::AnimationProgressed(animation);
610 
611     // Then special case the position of the tab being moved.
612     int x = animation_.CurrentValueBetween(start_bounds_.x(),
613                                            target_bounds_.x());
614     int width = animation_.CurrentValueBetween(start_bounds_.width(),
615                                                target_bounds_.width());
616     gfx::Rect tab_bounds(x, start_bounds_.y(), width,
617                          start_bounds_.height());
618     tabstrip_->SetTabBounds(tab_, tab_bounds);
619   }
620 
AnimationEnded(const ui::Animation * animation)621   virtual void AnimationEnded(const ui::Animation* animation) {
622     tabstrip_->needs_resize_layout_ = false;
623     TabStripGtk::TabAnimation::AnimationEnded(animation);
624   }
625 
GetGapWidth(int index)626   virtual double GetGapWidth(int index) {
627     if (to_index_ < from_index_) {
628       // The tab was made mini.
629       if (index == to_index_) {
630         double current_size =
631             animation_.CurrentValueBetween(0, target_bounds_.width());
632         if (current_size < -kTabHOffset)
633           return -(current_size + kTabHOffset);
634       } else if (index == from_index_ + 1) {
635         return animation_.CurrentValueBetween(start_bounds_.width(), 0);
636       }
637     } else {
638       // The tab was was made a normal tab.
639       if (index == from_index_) {
640         return animation_.CurrentValueBetween(
641             TabGtk::GetMiniWidth() + kTabHOffset, 0);
642       }
643     }
644     return 0;
645   }
646 
647  protected:
648   // Overridden from TabStripGtk::TabAnimation:
GetDuration() const649   virtual int GetDuration() const { return kReorderAnimationDurationMs; }
650 
GetWidthForTab(int index) const651   virtual double GetWidthForTab(int index) const {
652     TabGtk* tab = tabstrip_->GetTabAt(index);
653 
654     if (index == to_index_)
655       return animation_.CurrentValueBetween(0, target_bounds_.width());
656 
657     if (tab->mini())
658       return TabGtk::GetMiniWidth();
659 
660     if (tab->IsSelected()) {
661       return animation_.CurrentValueBetween(start_selected_width_,
662                                             end_selected_width_);
663     }
664 
665     return animation_.CurrentValueBetween(start_unselected_width_,
666                                           end_unselected_width_);
667   }
668 
669  private:
670   // The tab being moved.
671   TabGtk* tab_;
672 
673   // Initial bounds of tab_.
674   gfx::Rect start_bounds_;
675 
676   // Target bounds.
677   gfx::Rect target_bounds_;
678 
679   // Start and end indices of the tab.
680   int from_index_;
681   int to_index_;
682 
683   DISALLOW_COPY_AND_ASSIGN(MiniMoveAnimation);
684 };
685 
686 ////////////////////////////////////////////////////////////////////////////////
687 // TabStripGtk, public:
688 
689 // static
690 const int TabStripGtk::mini_to_non_mini_gap_ = 3;
691 
TabStripGtk(TabStripModel * model,BrowserWindowGtk * window)692 TabStripGtk::TabStripGtk(TabStripModel* model, BrowserWindowGtk* window)
693     : current_unselected_width_(TabGtk::GetStandardSize().width()),
694       current_selected_width_(TabGtk::GetStandardSize().width()),
695       available_width_for_tabs_(-1),
696       needs_resize_layout_(false),
697       tab_vertical_offset_(0),
698       model_(model),
699       window_(window),
700       theme_service_(GtkThemeService::GetFrom(model->profile())),
701       resize_layout_factory_(this),
702       added_as_message_loop_observer_(false) {
703   theme_service_->InitThemesFor(this);
704   registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
705                  NotificationService::AllSources());
706 }
707 
~TabStripGtk()708 TabStripGtk::~TabStripGtk() {
709   model_->RemoveObserver(this);
710   tabstrip_.Destroy();
711 
712   // Free any remaining tabs.  This is needed to free the very last tab,
713   // because it is not animated on close.  This also happens when all of the
714   // tabs are closed at once.
715   std::vector<TabData>::iterator iterator = tab_data_.begin();
716   for (; iterator < tab_data_.end(); iterator++) {
717     delete iterator->tab;
718   }
719 
720   tab_data_.clear();
721 
722   // Make sure we unhook ourselves as a message loop observer so that we don't
723   // crash in the case where the user closes the last tab in a window.
724   RemoveMessageLoopObserver();
725 }
726 
Init()727 void TabStripGtk::Init() {
728   model_->AddObserver(this);
729 
730   tabstrip_.Own(gtk_fixed_new());
731   ViewIDUtil::SetID(tabstrip_.get(), VIEW_ID_TAB_STRIP);
732   // We want the tab strip to be horizontally shrinkable, so that the Chrome
733   // window can be resized freely.
734   gtk_widget_set_size_request(tabstrip_.get(), 0,
735                               TabGtk::GetMinimumUnselectedSize().height());
736   gtk_widget_set_app_paintable(tabstrip_.get(), TRUE);
737   gtk_drag_dest_set(tabstrip_.get(), GTK_DEST_DEFAULT_ALL,
738                     NULL, 0,
739                     static_cast<GdkDragAction>(
740                         GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
741   static const int targets[] = { ui::TEXT_URI_LIST,
742                                  ui::NETSCAPE_URL,
743                                  ui::TEXT_PLAIN,
744                                  -1 };
745   ui::SetDestTargetList(tabstrip_.get(), targets);
746 
747   g_signal_connect(tabstrip_.get(), "expose-event",
748                    G_CALLBACK(OnExposeThunk), this);
749   g_signal_connect(tabstrip_.get(), "size-allocate",
750                    G_CALLBACK(OnSizeAllocateThunk), this);
751   g_signal_connect(tabstrip_.get(), "drag-motion",
752                    G_CALLBACK(OnDragMotionThunk), this);
753   g_signal_connect(tabstrip_.get(), "drag-drop",
754                    G_CALLBACK(OnDragDropThunk), this);
755   g_signal_connect(tabstrip_.get(), "drag-leave",
756                    G_CALLBACK(OnDragLeaveThunk), this);
757   g_signal_connect(tabstrip_.get(), "drag-data-received",
758                    G_CALLBACK(OnDragDataReceivedThunk), this);
759 
760   newtab_button_.reset(MakeNewTabButton());
761 
762   gtk_widget_show_all(tabstrip_.get());
763 
764   bounds_ = GetInitialWidgetBounds(tabstrip_.get());
765 
766   if (drop_indicator_width == 0) {
767     // Direction doesn't matter, both images are the same size.
768     GdkPixbuf* drop_image = GetDropArrowImage(true);
769     drop_indicator_width = gdk_pixbuf_get_width(drop_image);
770     drop_indicator_height = gdk_pixbuf_get_height(drop_image);
771   }
772 
773   ViewIDUtil::SetDelegateForWidget(widget(), this);
774 }
775 
Show()776 void TabStripGtk::Show() {
777   gtk_widget_show(tabstrip_.get());
778 }
779 
Hide()780 void TabStripGtk::Hide() {
781   gtk_widget_hide(tabstrip_.get());
782 }
783 
IsActiveDropTarget() const784 bool TabStripGtk::IsActiveDropTarget() const {
785   for (int i = 0; i < GetTabCount(); ++i) {
786     TabGtk* tab = GetTabAt(i);
787     if (tab->dragging())
788       return true;
789   }
790   return false;
791 }
792 
Layout()793 void TabStripGtk::Layout() {
794   // Called from:
795   // - window resize
796   // - animation completion
797   StopAnimation();
798 
799   GenerateIdealBounds();
800   int tab_count = GetTabCount();
801   int tab_right = 0;
802   for (int i = 0; i < tab_count; ++i) {
803     const gfx::Rect& bounds = tab_data_.at(i).ideal_bounds;
804     TabGtk* tab = GetTabAt(i);
805     tab->set_animating_mini_change(false);
806     tab->set_vertical_offset(tab_vertical_offset_);
807     SetTabBounds(tab, bounds);
808     tab_right = bounds.right();
809     tab_right += GetTabHOffset(i + 1);
810   }
811 
812   LayoutNewTabButton(static_cast<double>(tab_right), current_unselected_width_);
813 }
814 
SchedulePaint()815 void TabStripGtk::SchedulePaint() {
816   gtk_widget_queue_draw(tabstrip_.get());
817 }
818 
SetBounds(const gfx::Rect & bounds)819 void TabStripGtk::SetBounds(const gfx::Rect& bounds) {
820   bounds_ = bounds;
821 }
822 
UpdateLoadingAnimations()823 void TabStripGtk::UpdateLoadingAnimations() {
824   for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) {
825     TabGtk* current_tab = GetTabAt(i);
826     if (current_tab->closing()) {
827       --index;
828     } else {
829       TabRendererGtk::AnimationState state;
830       TabContentsWrapper* contents = model_->GetTabContentsAt(index);
831       if (!contents || !contents->tab_contents()->is_loading()) {
832         state = TabGtk::ANIMATION_NONE;
833       } else if (contents->tab_contents()->waiting_for_response()) {
834         state = TabGtk::ANIMATION_WAITING;
835       } else {
836         state = TabGtk::ANIMATION_LOADING;
837       }
838       if (current_tab->ValidateLoadingAnimation(state)) {
839         // Queue the tab's icon area to be repainted.
840         gfx::Rect favicon_bounds = current_tab->favicon_bounds();
841         gtk_widget_queue_draw_area(tabstrip_.get(),
842             favicon_bounds.x() + current_tab->x(),
843             favicon_bounds.y() + current_tab->y(),
844             favicon_bounds.width(),
845             favicon_bounds.height());
846       }
847     }
848   }
849 }
850 
IsCompatibleWith(TabStripGtk * other)851 bool TabStripGtk::IsCompatibleWith(TabStripGtk* other) {
852   return model_->profile() == other->model()->profile();
853 }
854 
IsAnimating() const855 bool TabStripGtk::IsAnimating() const {
856   return active_animation_.get() != NULL;
857 }
858 
DestroyDragController()859 void TabStripGtk::DestroyDragController() {
860   drag_controller_.reset();
861 }
862 
DestroyDraggedSourceTab(TabGtk * tab)863 void TabStripGtk::DestroyDraggedSourceTab(TabGtk* tab) {
864   // We could be running an animation that references this Tab.
865   StopAnimation();
866 
867   // Make sure we leave the tab_data_ vector in a consistent state, otherwise
868   // we'll be pointing to tabs that have been deleted and removed from the
869   // child view list.
870   std::vector<TabData>::iterator it = tab_data_.begin();
871   for (; it != tab_data_.end(); ++it) {
872     if (it->tab == tab) {
873       if (!model_->closing_all())
874         NOTREACHED() << "Leaving in an inconsistent state!";
875       tab_data_.erase(it);
876       break;
877     }
878   }
879 
880   gtk_container_remove(GTK_CONTAINER(tabstrip_.get()), tab->widget());
881   // If we delete the dragged source tab here, the DestroyDragWidget posted
882   // task will be run after the tab is deleted, leading to a crash.
883   MessageLoop::current()->DeleteSoon(FROM_HERE, tab);
884 
885   // Force a layout here, because if we've just quickly drag detached a Tab,
886   // the stopping of the active animation above may have left the TabStrip in a
887   // bad (visual) state.
888   Layout();
889 }
890 
GetIdealBounds(int index)891 gfx::Rect TabStripGtk::GetIdealBounds(int index) {
892   DCHECK(index >= 0 && index < GetTabCount());
893   return tab_data_.at(index).ideal_bounds;
894 }
895 
SetVerticalOffset(int offset)896 void TabStripGtk::SetVerticalOffset(int offset) {
897   tab_vertical_offset_ = offset;
898   Layout();
899 }
900 
GetTabStripOriginForWidget(GtkWidget * target)901 gfx::Point TabStripGtk::GetTabStripOriginForWidget(GtkWidget* target) {
902   int x, y;
903   if (!gtk_widget_translate_coordinates(widget(), target,
904       -widget()->allocation.x, 0, &x, &y)) {
905     // If the tab strip isn't showing, give the coordinates relative to the
906     // toplevel instead.
907     if (!gtk_widget_translate_coordinates(
908         gtk_widget_get_toplevel(widget()), target, 0, 0, &x, &y)) {
909       NOTREACHED();
910     }
911   }
912   if (GTK_WIDGET_NO_WINDOW(target)) {
913     x += target->allocation.x;
914     y += target->allocation.y;
915   }
916   return gfx::Point(x, y);
917 }
918 
919 ////////////////////////////////////////////////////////////////////////////////
920 // ViewIDUtil::Delegate implementation
921 
GetWidgetForViewID(ViewID view_id)922 GtkWidget* TabStripGtk::GetWidgetForViewID(ViewID view_id) {
923   if (GetTabCount() > 0) {
924     if (view_id == VIEW_ID_TAB_LAST) {
925       return GetTabAt(GetTabCount() - 1)->widget();
926     } else if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) {
927       int index = view_id - VIEW_ID_TAB_0;
928       if (index >= 0 && index < GetTabCount()) {
929         return GetTabAt(index)->widget();
930       } else {
931         return NULL;
932       }
933     }
934   }
935 
936   return NULL;
937 }
938 
939 ////////////////////////////////////////////////////////////////////////////////
940 // TabStripGtk, TabStripModelObserver implementation:
941 
TabInsertedAt(TabContentsWrapper * contents,int index,bool foreground)942 void TabStripGtk::TabInsertedAt(TabContentsWrapper* contents,
943                                 int index,
944                                 bool foreground) {
945   DCHECK(contents);
946   DCHECK(index == TabStripModel::kNoTab || model_->ContainsIndex(index));
947 
948   StopAnimation();
949 
950   bool contains_tab = false;
951   TabGtk* tab = NULL;
952   // First see if this Tab is one that was dragged out of this TabStrip and is
953   // now being dragged back in. In this case, the DraggedTabController actually
954   // has the Tab already constructed and we can just insert it into our list
955   // again.
956   if (IsDragSessionActive()) {
957     tab = drag_controller_->GetDragSourceTabForContents(
958         contents->tab_contents());
959     if (tab) {
960       // If the Tab was detached, it would have been animated closed but not
961       // removed, so we need to reset this property.
962       tab->set_closing(false);
963       tab->ValidateLoadingAnimation(TabRendererGtk::ANIMATION_NONE);
964       tab->SetVisible(true);
965     }
966 
967     // See if we're already in the list. We don't want to add ourselves twice.
968     std::vector<TabData>::const_iterator iter = tab_data_.begin();
969     for (; iter != tab_data_.end() && !contains_tab; ++iter) {
970       if (iter->tab == tab)
971         contains_tab = true;
972     }
973   }
974 
975   if (!tab)
976     tab = new TabGtk(this);
977 
978   // Only insert if we're not already in the list.
979   if (!contains_tab) {
980     TabData d = { tab, gfx::Rect() };
981     tab_data_.insert(tab_data_.begin() + index, d);
982     tab->UpdateData(contents->tab_contents(), model_->IsAppTab(index), false);
983   }
984   tab->set_mini(model_->IsMiniTab(index));
985   tab->set_app(model_->IsAppTab(index));
986   tab->SetBlocked(model_->IsTabBlocked(index));
987 
988   if (gtk_widget_get_parent(tab->widget()) != tabstrip_.get())
989     gtk_fixed_put(GTK_FIXED(tabstrip_.get()), tab->widget(), 0, 0);
990 
991   // Don't animate the first tab; it looks weird.
992   if (GetTabCount() > 1) {
993     StartInsertTabAnimation(index);
994     // We added the tab at 0x0, we need to force an animation step otherwise
995     // if GTK paints before the animation event the tab is painted at 0x0
996     // which is most likely not where it should be positioned.
997     active_animation_->AnimationProgressed(NULL);
998   } else {
999     Layout();
1000   }
1001 }
1002 
TabDetachedAt(TabContentsWrapper * contents,int index)1003 void TabStripGtk::TabDetachedAt(TabContentsWrapper* contents, int index) {
1004   GenerateIdealBounds();
1005   StartRemoveTabAnimation(index, contents->tab_contents());
1006   // Have to do this _after_ calling StartRemoveTabAnimation, so that any
1007   // previous remove is completed fully and index is valid in sync with the
1008   // model index.
1009   GetTabAt(index)->set_closing(true);
1010 }
1011 
TabSelectedAt(TabContentsWrapper * old_contents,TabContentsWrapper * new_contents,int index,bool user_gesture)1012 void TabStripGtk::TabSelectedAt(TabContentsWrapper* old_contents,
1013                                 TabContentsWrapper* new_contents,
1014                                 int index,
1015                                 bool user_gesture) {
1016   DCHECK(index >= 0 && index < static_cast<int>(GetTabCount()));
1017 
1018   // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
1019   // a different size to the selected ones.
1020   bool tiny_tabs = current_unselected_width_ != current_selected_width_;
1021   if (!IsAnimating() && (!needs_resize_layout_ || tiny_tabs))
1022     Layout();
1023 
1024   GetTabAt(index)->SchedulePaint();
1025 
1026   int old_index = model_->GetIndexOfTabContents(old_contents);
1027   if (old_index >= 0) {
1028     GetTabAt(old_index)->SchedulePaint();
1029     GetTabAt(old_index)->StopMiniTabTitleAnimation();
1030   }
1031 }
1032 
TabMoved(TabContentsWrapper * contents,int from_index,int to_index)1033 void TabStripGtk::TabMoved(TabContentsWrapper* contents,
1034                            int from_index,
1035                            int to_index) {
1036   gfx::Rect start_bounds = GetIdealBounds(from_index);
1037   TabGtk* tab = GetTabAt(from_index);
1038   tab_data_.erase(tab_data_.begin() + from_index);
1039   TabData data = {tab, gfx::Rect()};
1040   tab->set_mini(model_->IsMiniTab(to_index));
1041   tab->SetBlocked(model_->IsTabBlocked(to_index));
1042   tab_data_.insert(tab_data_.begin() + to_index, data);
1043   GenerateIdealBounds();
1044   StartMoveTabAnimation(from_index, to_index);
1045 }
1046 
TabChangedAt(TabContentsWrapper * contents,int index,TabChangeType change_type)1047 void TabStripGtk::TabChangedAt(TabContentsWrapper* contents, int index,
1048                                TabChangeType change_type) {
1049   // Index is in terms of the model. Need to make sure we adjust that index in
1050   // case we have an animation going.
1051   TabGtk* tab = GetTabAtAdjustForAnimation(index);
1052   if (change_type == TITLE_NOT_LOADING) {
1053     if (tab->mini() && !tab->IsSelected())
1054       tab->StartMiniTabTitleAnimation();
1055     // We'll receive another notification of the change asynchronously.
1056     return;
1057   }
1058   tab->UpdateData(contents->tab_contents(),
1059                   model_->IsAppTab(index),
1060                   change_type == LOADING_ONLY);
1061   tab->UpdateFromModel();
1062 }
1063 
TabReplacedAt(TabStripModel * tab_strip_model,TabContentsWrapper * old_contents,TabContentsWrapper * new_contents,int index)1064 void TabStripGtk::TabReplacedAt(TabStripModel* tab_strip_model,
1065                                 TabContentsWrapper* old_contents,
1066                                 TabContentsWrapper* new_contents,
1067                                 int index) {
1068   TabChangedAt(new_contents, index, ALL);
1069 }
1070 
TabMiniStateChanged(TabContentsWrapper * contents,int index)1071 void TabStripGtk::TabMiniStateChanged(TabContentsWrapper* contents, int index) {
1072   // Don't do anything if we've already picked up the change from TabMoved.
1073   if (GetTabAt(index)->mini() == model_->IsMiniTab(index))
1074     return;
1075 
1076   GetTabAt(index)->set_mini(model_->IsMiniTab(index));
1077   // Don't animate if the window isn't visible yet. The window won't be visible
1078   // when dragging a mini-tab to a new window.
1079   if (window_ && window_->window() &&
1080       GTK_WIDGET_VISIBLE(GTK_WIDGET(window_->window()))) {
1081     StartMiniTabAnimation(index);
1082   } else {
1083     Layout();
1084   }
1085 }
1086 
TabBlockedStateChanged(TabContentsWrapper * contents,int index)1087 void TabStripGtk::TabBlockedStateChanged(TabContentsWrapper* contents,
1088                                          int index) {
1089   GetTabAt(index)->SetBlocked(model_->IsTabBlocked(index));
1090 }
1091 
1092 ////////////////////////////////////////////////////////////////////////////////
1093 // TabStripGtk, TabGtk::TabDelegate implementation:
1094 
IsTabSelected(const TabGtk * tab) const1095 bool TabStripGtk::IsTabSelected(const TabGtk* tab) const {
1096   if (tab->closing())
1097     return false;
1098 
1099   return GetIndexOfTab(tab) == model_->active_index();
1100 }
1101 
IsTabDetached(const TabGtk * tab) const1102 bool TabStripGtk::IsTabDetached(const TabGtk* tab) const {
1103   if (drag_controller_.get())
1104     return drag_controller_->IsTabDetached(tab);
1105   return false;
1106 }
1107 
GetCurrentTabWidths(double * unselected_width,double * selected_width) const1108 void TabStripGtk::GetCurrentTabWidths(double* unselected_width,
1109                                       double* selected_width) const {
1110   *unselected_width = current_unselected_width_;
1111   *selected_width = current_selected_width_;
1112 }
1113 
IsTabPinned(const TabGtk * tab) const1114 bool TabStripGtk::IsTabPinned(const TabGtk* tab) const {
1115   if (tab->closing())
1116     return false;
1117 
1118   return model_->IsTabPinned(GetIndexOfTab(tab));
1119 }
1120 
SelectTab(TabGtk * tab)1121 void TabStripGtk::SelectTab(TabGtk* tab) {
1122   int index = GetIndexOfTab(tab);
1123   if (model_->ContainsIndex(index))
1124     model_->ActivateTabAt(index, true);
1125 }
1126 
CloseTab(TabGtk * tab)1127 void TabStripGtk::CloseTab(TabGtk* tab) {
1128   int tab_index = GetIndexOfTab(tab);
1129   if (model_->ContainsIndex(tab_index)) {
1130     TabGtk* last_tab = GetTabAt(GetTabCount() - 1);
1131     // Limit the width available to the TabStrip for laying out Tabs, so that
1132     // Tabs are not resized until a later time (when the mouse pointer leaves
1133     // the TabStrip).
1134     available_width_for_tabs_ = GetAvailableWidthForTabs(last_tab);
1135     needs_resize_layout_ = true;
1136     // We hook into the message loop in order to receive mouse move events when
1137     // the mouse is outside of the tabstrip.  We unhook once the resize layout
1138     // animation is started.
1139     AddMessageLoopObserver();
1140     model_->CloseTabContentsAt(tab_index,
1141                                TabStripModel::CLOSE_USER_GESTURE |
1142                                TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
1143   }
1144 }
1145 
IsCommandEnabledForTab(TabStripModel::ContextMenuCommand command_id,const TabGtk * tab) const1146 bool TabStripGtk::IsCommandEnabledForTab(
1147     TabStripModel::ContextMenuCommand command_id, const TabGtk* tab) const {
1148   int index = GetIndexOfTab(tab);
1149   if (model_->ContainsIndex(index))
1150     return model_->IsContextMenuCommandEnabled(index, command_id);
1151   return false;
1152 }
1153 
ExecuteCommandForTab(TabStripModel::ContextMenuCommand command_id,TabGtk * tab)1154 void TabStripGtk::ExecuteCommandForTab(
1155     TabStripModel::ContextMenuCommand command_id, TabGtk* tab) {
1156   int index = GetIndexOfTab(tab);
1157   if (model_->ContainsIndex(index))
1158     model_->ExecuteContextMenuCommand(index, command_id);
1159 }
1160 
StartHighlightTabsForCommand(TabStripModel::ContextMenuCommand command_id,TabGtk * tab)1161 void TabStripGtk::StartHighlightTabsForCommand(
1162     TabStripModel::ContextMenuCommand command_id, TabGtk* tab) {
1163   if (command_id == TabStripModel::CommandCloseOtherTabs ||
1164       command_id == TabStripModel::CommandCloseTabsToRight) {
1165     NOTIMPLEMENTED();
1166   }
1167 }
1168 
StopHighlightTabsForCommand(TabStripModel::ContextMenuCommand command_id,TabGtk * tab)1169 void TabStripGtk::StopHighlightTabsForCommand(
1170     TabStripModel::ContextMenuCommand command_id, TabGtk* tab) {
1171   if (command_id == TabStripModel::CommandCloseTabsToRight ||
1172       command_id == TabStripModel::CommandCloseOtherTabs) {
1173     // Just tell all Tabs to stop pulsing - it's safe.
1174     StopAllHighlighting();
1175   }
1176 }
1177 
StopAllHighlighting()1178 void TabStripGtk::StopAllHighlighting() {
1179   // TODO(jhawkins): Hook up animations.
1180   NOTIMPLEMENTED();
1181 }
1182 
MaybeStartDrag(TabGtk * tab,const gfx::Point & point)1183 void TabStripGtk::MaybeStartDrag(TabGtk* tab, const gfx::Point& point) {
1184   // Don't accidentally start any drag operations during animations if the
1185   // mouse is down.
1186   if (IsAnimating() || tab->closing() || !HasAvailableDragActions())
1187     return;
1188 
1189   drag_controller_.reset(new DraggedTabControllerGtk(tab, this));
1190   drag_controller_->CaptureDragInfo(point);
1191 }
1192 
ContinueDrag(GdkDragContext * context)1193 void TabStripGtk::ContinueDrag(GdkDragContext* context) {
1194   // We can get called even if |MaybeStartDrag| wasn't called in the event of
1195   // a TabStrip animation when the mouse button is down. In this case we should
1196   // _not_ continue the drag because it can lead to weird bugs.
1197   if (drag_controller_.get())
1198     drag_controller_->Drag();
1199 }
1200 
EndDrag(bool canceled)1201 bool TabStripGtk::EndDrag(bool canceled) {
1202   return drag_controller_.get() ? drag_controller_->EndDrag(canceled) : false;
1203 }
1204 
HasAvailableDragActions() const1205 bool TabStripGtk::HasAvailableDragActions() const {
1206   return model_->delegate()->GetDragActions() != 0;
1207 }
1208 
GetThemeProvider()1209 ui::ThemeProvider* TabStripGtk::GetThemeProvider() {
1210   return theme_service_;
1211 }
1212 
1213 ///////////////////////////////////////////////////////////////////////////////
1214 // TabStripGtk, MessageLoop::Observer implementation:
1215 
WillProcessEvent(GdkEvent * event)1216 void TabStripGtk::WillProcessEvent(GdkEvent* event) {
1217   // Nothing to do.
1218 }
1219 
DidProcessEvent(GdkEvent * event)1220 void TabStripGtk::DidProcessEvent(GdkEvent* event) {
1221   switch (event->type) {
1222     case GDK_MOTION_NOTIFY:
1223     case GDK_LEAVE_NOTIFY:
1224       HandleGlobalMouseMoveEvent();
1225       break;
1226     default:
1227       break;
1228   }
1229 }
1230 
1231 ///////////////////////////////////////////////////////////////////////////////
1232 // TabStripGtk, NotificationObserver implementation:
1233 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)1234 void TabStripGtk::Observe(NotificationType type,
1235                           const NotificationSource& source,
1236                           const NotificationDetails& details) {
1237   if (type == NotificationType::BROWSER_THEME_CHANGED) {
1238     TabRendererGtk::SetSelectedTitleColor(theme_service_->GetColor(
1239         ThemeService::COLOR_TAB_TEXT));
1240     TabRendererGtk::SetUnselectedTitleColor(theme_service_->GetColor(
1241         ThemeService::COLOR_BACKGROUND_TAB_TEXT));
1242   }
1243 }
1244 
1245 ////////////////////////////////////////////////////////////////////////////////
1246 // TabStripGtk, private:
1247 
GetTabCount() const1248 int TabStripGtk::GetTabCount() const {
1249   return static_cast<int>(tab_data_.size());
1250 }
1251 
GetMiniTabCount() const1252 int TabStripGtk::GetMiniTabCount() const {
1253   int mini_count = 0;
1254   for (size_t i = 0; i < tab_data_.size(); ++i) {
1255     if (tab_data_[i].tab->mini())
1256       mini_count++;
1257     else
1258       return mini_count;
1259   }
1260   return mini_count;
1261 }
1262 
GetAvailableWidthForTabs(TabGtk * last_tab) const1263 int TabStripGtk::GetAvailableWidthForTabs(TabGtk* last_tab) const {
1264   if (!base::i18n::IsRTL())
1265     return last_tab->x() - bounds_.x() + last_tab->width();
1266   else
1267     return bounds_.width() - last_tab->x();
1268 }
1269 
GetIndexOfTab(const TabGtk * tab) const1270 int TabStripGtk::GetIndexOfTab(const TabGtk* tab) const {
1271   for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) {
1272     TabGtk* current_tab = GetTabAt(i);
1273     if (current_tab->closing()) {
1274       --index;
1275     } else if (current_tab == tab) {
1276       return index;
1277     }
1278   }
1279   return -1;
1280 }
1281 
GetTabAt(int index) const1282 TabGtk* TabStripGtk::GetTabAt(int index) const {
1283   DCHECK_GE(index, 0);
1284   DCHECK_LT(index, GetTabCount());
1285   return tab_data_.at(index).tab;
1286 }
1287 
GetTabAtAdjustForAnimation(int index) const1288 TabGtk* TabStripGtk::GetTabAtAdjustForAnimation(int index) const {
1289   if (active_animation_.get() &&
1290       active_animation_->type() == TabAnimation::REMOVE &&
1291       index >=
1292       static_cast<RemoveTabAnimation*>(active_animation_.get())->index()) {
1293     index++;
1294   }
1295   return GetTabAt(index);
1296 }
1297 
RemoveTabAt(int index)1298 void TabStripGtk::RemoveTabAt(int index) {
1299   TabGtk* removed = tab_data_.at(index).tab;
1300 
1301   // Remove the Tab from the TabStrip's list.
1302   tab_data_.erase(tab_data_.begin() + index);
1303 
1304   if (!IsDragSessionActive() || !drag_controller_->IsDragSourceTab(removed)) {
1305     gtk_container_remove(GTK_CONTAINER(tabstrip_.get()), removed->widget());
1306     delete removed;
1307   }
1308 }
1309 
HandleGlobalMouseMoveEvent()1310 void TabStripGtk::HandleGlobalMouseMoveEvent() {
1311   if (!IsCursorInTabStripZone()) {
1312     // Mouse moved outside the tab slop zone, start a timer to do a resize
1313     // layout after a short while...
1314     if (resize_layout_factory_.empty()) {
1315       MessageLoop::current()->PostDelayedTask(FROM_HERE,
1316           resize_layout_factory_.NewRunnableMethod(
1317               &TabStripGtk::ResizeLayoutTabs),
1318           kResizeTabsTimeMs);
1319     }
1320   } else {
1321     // Mouse moved quickly out of the tab strip and then into it again, so
1322     // cancel the timer so that the strip doesn't move when the mouse moves
1323     // back over it.
1324     resize_layout_factory_.RevokeAll();
1325   }
1326 }
1327 
GenerateIdealBounds()1328 void TabStripGtk::GenerateIdealBounds() {
1329   int tab_count = GetTabCount();
1330   double unselected, selected;
1331   GetDesiredTabWidths(tab_count, GetMiniTabCount(), &unselected, &selected);
1332 
1333   current_unselected_width_ = unselected;
1334   current_selected_width_ = selected;
1335 
1336   // NOTE: This currently assumes a tab's height doesn't differ based on
1337   // selected state or the number of tabs in the strip!
1338   int tab_height = TabGtk::GetStandardSize().height();
1339   double tab_x = tab_start_x();
1340   for (int i = 0; i < tab_count; ++i) {
1341     TabGtk* tab = GetTabAt(i);
1342     double tab_width = unselected;
1343     if (tab->mini())
1344       tab_width = TabGtk::GetMiniWidth();
1345     else if (tab->IsSelected())
1346       tab_width = selected;
1347     double end_of_tab = tab_x + tab_width;
1348     int rounded_tab_x = Round(tab_x);
1349     gfx::Rect state(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
1350                     tab_height);
1351     tab_data_.at(i).ideal_bounds = state;
1352     tab_x = end_of_tab + GetTabHOffset(i + 1);
1353   }
1354 }
1355 
LayoutNewTabButton(double last_tab_right,double unselected_width)1356 void TabStripGtk::LayoutNewTabButton(double last_tab_right,
1357                                      double unselected_width) {
1358   gfx::Rect bounds(0, kNewTabButtonVOffset,
1359                    newtab_button_->width(), newtab_button_->height());
1360   int delta = abs(Round(unselected_width) - TabGtk::GetStandardSize().width());
1361   if (delta > 1 && !needs_resize_layout_) {
1362     // We're shrinking tabs, so we need to anchor the New Tab button to the
1363     // right edge of the TabStrip's bounds, rather than the right edge of the
1364     // right-most Tab, otherwise it'll bounce when animating.
1365     bounds.set_x(bounds_.width() - newtab_button_->width());
1366   } else {
1367     bounds.set_x(Round(last_tab_right - kTabHOffset) + kNewTabButtonHOffset);
1368   }
1369   bounds.set_x(gtk_util::MirroredLeftPointForRect(tabstrip_.get(), bounds));
1370 
1371   gtk_fixed_move(GTK_FIXED(tabstrip_.get()), newtab_button_->widget(),
1372                  bounds.x(), bounds.y());
1373 }
1374 
GetDesiredTabWidths(int tab_count,int mini_tab_count,double * unselected_width,double * selected_width) const1375 void TabStripGtk::GetDesiredTabWidths(int tab_count,
1376                                       int mini_tab_count,
1377                                       double* unselected_width,
1378                                       double* selected_width) const {
1379   DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count);
1380   const double min_unselected_width =
1381       TabGtk::GetMinimumUnselectedSize().width();
1382   const double min_selected_width =
1383       TabGtk::GetMinimumSelectedSize().width();
1384 
1385   *unselected_width = min_unselected_width;
1386   *selected_width = min_selected_width;
1387 
1388   if (tab_count == 0) {
1389     // Return immediately to avoid divide-by-zero below.
1390     return;
1391   }
1392 
1393   // Determine how much space we can actually allocate to tabs.
1394   int available_width = tabstrip_->allocation.width;
1395   if (available_width_for_tabs_ < 0) {
1396     available_width = bounds_.width();
1397     available_width -=
1398         (kNewTabButtonHOffset + newtab_button_->width());
1399   } else {
1400     // Interesting corner case: if |available_width_for_tabs_| > the result
1401     // of the calculation in the conditional arm above, the strip is in
1402     // overflow.  We can either use the specified width or the true available
1403     // width here; the first preserves the consistent "leave the last tab under
1404     // the user's mouse so they can close many tabs" behavior at the cost of
1405     // prolonging the glitchy appearance of the overflow state, while the second
1406     // gets us out of overflow as soon as possible but forces the user to move
1407     // their mouse for a few tabs' worth of closing.  We choose visual
1408     // imperfection over behavioral imperfection and select the first option.
1409     available_width = available_width_for_tabs_;
1410   }
1411 
1412   if (mini_tab_count > 0) {
1413     available_width -= mini_tab_count * (TabGtk::GetMiniWidth() + kTabHOffset);
1414     tab_count -= mini_tab_count;
1415     if (tab_count == 0) {
1416       *selected_width = *unselected_width = TabGtk::GetStandardSize().width();
1417       return;
1418     }
1419     // Account for gap between the last mini-tab and first normal tab.
1420     available_width -= mini_to_non_mini_gap_;
1421   }
1422 
1423   // Calculate the desired tab widths by dividing the available space into equal
1424   // portions.  Don't let tabs get larger than the "standard width" or smaller
1425   // than the minimum width for each type, respectively.
1426   const int total_offset = kTabHOffset * (tab_count - 1);
1427   const double desired_tab_width = std::min(
1428       (static_cast<double>(available_width - total_offset) /
1429        static_cast<double>(tab_count)),
1430       static_cast<double>(TabGtk::GetStandardSize().width()));
1431 
1432   *unselected_width = std::max(desired_tab_width, min_unselected_width);
1433   *selected_width = std::max(desired_tab_width, min_selected_width);
1434 
1435   // When there are multiple tabs, we'll have one selected and some unselected
1436   // tabs.  If the desired width was between the minimum sizes of these types,
1437   // try to shrink the tabs with the smaller minimum.  For example, if we have
1438   // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5.  If
1439   // selected tabs have a minimum width of 4 and unselected tabs have a minimum
1440   // width of 1, the above code would set *unselected_width = 2.5,
1441   // *selected_width = 4, which results in a total width of 11.5.  Instead, we
1442   // want to set *unselected_width = 2, *selected_width = 4, for a total width
1443   // of 10.
1444   if (tab_count > 1) {
1445     if ((min_unselected_width < min_selected_width) &&
1446         (desired_tab_width < min_selected_width)) {
1447       double calc_width =
1448           static_cast<double>(
1449               available_width - total_offset - min_selected_width) /
1450           static_cast<double>(tab_count - 1);
1451       *unselected_width = std::max(calc_width, min_unselected_width);
1452     } else if ((min_unselected_width > min_selected_width) &&
1453                (desired_tab_width < min_unselected_width)) {
1454       *selected_width = std::max(available_width - total_offset -
1455           (min_unselected_width * (tab_count - 1)), min_selected_width);
1456     }
1457   }
1458 }
1459 
GetTabHOffset(int tab_index)1460 int TabStripGtk::GetTabHOffset(int tab_index) {
1461   if (tab_index < GetTabCount() && GetTabAt(tab_index - 1)->mini() &&
1462       !GetTabAt(tab_index)->mini()) {
1463     return mini_to_non_mini_gap_ + kTabHOffset;
1464   }
1465   return kTabHOffset;
1466 }
1467 
tab_start_x() const1468 int TabStripGtk::tab_start_x() const {
1469   return 0;
1470 }
1471 
ResizeLayoutTabs()1472 bool TabStripGtk::ResizeLayoutTabs() {
1473   resize_layout_factory_.RevokeAll();
1474 
1475   // It is critically important that this is unhooked here, otherwise we will
1476   // keep spying on messages forever.
1477   RemoveMessageLoopObserver();
1478 
1479   available_width_for_tabs_ = -1;
1480   int mini_tab_count = GetMiniTabCount();
1481   if (mini_tab_count == GetTabCount()) {
1482     // Only mini tabs, we know the tab widths won't have changed (all mini-tabs
1483     // have the same width), so there is nothing to do.
1484     return false;
1485   }
1486   TabGtk* first_tab = GetTabAt(mini_tab_count);
1487   double unselected, selected;
1488   GetDesiredTabWidths(GetTabCount(), mini_tab_count, &unselected, &selected);
1489   int w = Round(first_tab->IsSelected() ? selected : unselected);
1490 
1491   // We only want to run the animation if we're not already at the desired
1492   // size.
1493   if (abs(first_tab->width() - w) > 1) {
1494     StartResizeLayoutAnimation();
1495     return true;
1496   }
1497 
1498   return false;
1499 }
1500 
IsCursorInTabStripZone() const1501 bool TabStripGtk::IsCursorInTabStripZone() const {
1502   gfx::Point tabstrip_topleft;
1503   gtk_util::ConvertWidgetPointToScreen(tabstrip_.get(), &tabstrip_topleft);
1504 
1505   gfx::Rect bds = bounds();
1506   bds.set_origin(tabstrip_topleft);
1507   bds.set_height(bds.height() + kTabStripAnimationVSlop);
1508 
1509   GdkScreen* screen = gdk_screen_get_default();
1510   GdkDisplay* display = gdk_screen_get_display(screen);
1511   gint x, y;
1512   gdk_display_get_pointer(display, NULL, &x, &y, NULL);
1513   gfx::Point cursor_point(x, y);
1514 
1515   return bds.Contains(cursor_point);
1516 }
1517 
AddMessageLoopObserver()1518 void TabStripGtk::AddMessageLoopObserver() {
1519   if (!added_as_message_loop_observer_) {
1520     MessageLoopForUI::current()->AddObserver(this);
1521     added_as_message_loop_observer_ = true;
1522   }
1523 }
1524 
RemoveMessageLoopObserver()1525 void TabStripGtk::RemoveMessageLoopObserver() {
1526   if (added_as_message_loop_observer_) {
1527     MessageLoopForUI::current()->RemoveObserver(this);
1528     added_as_message_loop_observer_ = false;
1529   }
1530 }
1531 
GetDropBounds(int drop_index,bool drop_before,bool * is_beneath)1532 gfx::Rect TabStripGtk::GetDropBounds(int drop_index,
1533                                      bool drop_before,
1534                                      bool* is_beneath) {
1535   DCHECK_NE(drop_index, -1);
1536   int center_x;
1537   if (drop_index < GetTabCount()) {
1538     TabGtk* tab = GetTabAt(drop_index);
1539     gfx::Rect bounds = tab->GetNonMirroredBounds(tabstrip_.get());
1540     // TODO(sky): update these for pinned tabs.
1541     if (drop_before)
1542       center_x = bounds.x() - (kTabHOffset / 2);
1543     else
1544       center_x = bounds.x() + (bounds.width() / 2);
1545   } else {
1546     TabGtk* last_tab = GetTabAt(drop_index - 1);
1547     gfx::Rect bounds = last_tab->GetNonMirroredBounds(tabstrip_.get());
1548     center_x = bounds.x() + bounds.width() + (kTabHOffset / 2);
1549   }
1550 
1551   center_x = gtk_util::MirroredXCoordinate(tabstrip_.get(), center_x);
1552 
1553   // Determine the screen bounds.
1554   gfx::Point drop_loc(center_x - drop_indicator_width / 2,
1555                       -drop_indicator_height);
1556   gtk_util::ConvertWidgetPointToScreen(tabstrip_.get(), &drop_loc);
1557   gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width,
1558                         drop_indicator_height);
1559 
1560   // TODO(jhawkins): We always display the arrow underneath the tab because we
1561   // don't have custom frame support yet.
1562   *is_beneath = true;
1563   if (*is_beneath)
1564     drop_bounds.Offset(0, drop_bounds.height() + bounds().height());
1565 
1566   return drop_bounds;
1567 }
1568 
UpdateDropIndex(GdkDragContext * context,gint x,gint y)1569 void TabStripGtk::UpdateDropIndex(GdkDragContext* context, gint x, gint y) {
1570   // If the UI layout is right-to-left, we need to mirror the mouse
1571   // coordinates since we calculate the drop index based on the
1572   // original (and therefore non-mirrored) positions of the tabs.
1573   x = gtk_util::MirroredXCoordinate(tabstrip_.get(), x);
1574   // We don't allow replacing the urls of mini-tabs.
1575   for (int i = GetMiniTabCount(); i < GetTabCount(); ++i) {
1576     TabGtk* tab = GetTabAt(i);
1577     gfx::Rect bounds = tab->GetNonMirroredBounds(tabstrip_.get());
1578     const int tab_max_x = bounds.x() + bounds.width();
1579     const int hot_width = bounds.width() / 3;
1580     if (x < tab_max_x) {
1581       if (x < bounds.x() + hot_width)
1582         SetDropIndex(i, true);
1583       else if (x >= tab_max_x - hot_width)
1584         SetDropIndex(i + 1, true);
1585       else
1586         SetDropIndex(i, false);
1587       return;
1588     }
1589   }
1590 
1591   // The drop isn't over a tab, add it to the end.
1592   SetDropIndex(GetTabCount(), true);
1593 }
1594 
SetDropIndex(int index,bool drop_before)1595 void TabStripGtk::SetDropIndex(int index, bool drop_before) {
1596   bool is_beneath;
1597   gfx::Rect drop_bounds = GetDropBounds(index, drop_before, &is_beneath);
1598 
1599   if (!drop_info_.get()) {
1600     drop_info_.reset(new DropInfo(index, drop_before, !is_beneath));
1601   } else {
1602     if (!GTK_IS_WIDGET(drop_info_->container)) {
1603       drop_info_->CreateContainer();
1604     } else if (drop_info_->drop_index == index &&
1605                drop_info_->drop_before == drop_before) {
1606       return;
1607     }
1608 
1609     drop_info_->drop_index = index;
1610     drop_info_->drop_before = drop_before;
1611     if (is_beneath == drop_info_->point_down) {
1612       drop_info_->point_down = !is_beneath;
1613       drop_info_->drop_arrow= GetDropArrowImage(drop_info_->point_down);
1614     }
1615   }
1616 
1617   gtk_window_move(GTK_WINDOW(drop_info_->container),
1618                   drop_bounds.x(), drop_bounds.y());
1619   gtk_window_resize(GTK_WINDOW(drop_info_->container),
1620                     drop_bounds.width(), drop_bounds.height());
1621 }
1622 
CompleteDrop(guchar * data,bool is_plain_text)1623 bool TabStripGtk::CompleteDrop(guchar* data, bool is_plain_text) {
1624   if (!drop_info_.get())
1625     return false;
1626 
1627   const int drop_index = drop_info_->drop_index;
1628   const bool drop_before = drop_info_->drop_before;
1629 
1630   // Destroy the drop indicator.
1631   drop_info_.reset();
1632 
1633   GURL url;
1634   if (is_plain_text) {
1635     AutocompleteMatch match;
1636     model_->profile()->GetAutocompleteClassifier()->Classify(
1637         UTF8ToUTF16(reinterpret_cast<char*>(data)), string16(), false,
1638         &match, NULL);
1639     url = match.destination_url;
1640   } else {
1641     std::string url_string(reinterpret_cast<char*>(data));
1642     url = GURL(url_string.substr(0, url_string.find_first_of('\n')));
1643   }
1644   if (!url.is_valid())
1645     return false;
1646 
1647   browser::NavigateParams params(window()->browser(), url,
1648                                  PageTransition::LINK);
1649   params.tabstrip_index = drop_index;
1650 
1651   if (drop_before) {
1652     params.disposition = NEW_FOREGROUND_TAB;
1653   } else {
1654     params.disposition = CURRENT_TAB;
1655     params.source_contents = model_->GetTabContentsAt(drop_index);
1656   }
1657 
1658   browser::Navigate(&params);
1659 
1660   return true;
1661 }
1662 
1663 // static
GetDropArrowImage(bool is_down)1664 GdkPixbuf* TabStripGtk::GetDropArrowImage(bool is_down) {
1665   return ResourceBundle::GetSharedInstance().GetPixbufNamed(
1666       is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
1667 }
1668 
1669 // TabStripGtk::DropInfo -------------------------------------------------------
1670 
DropInfo(int drop_index,bool drop_before,bool point_down)1671 TabStripGtk::DropInfo::DropInfo(int drop_index, bool drop_before,
1672                                 bool point_down)
1673     : drop_index(drop_index),
1674       drop_before(drop_before),
1675       point_down(point_down) {
1676   CreateContainer();
1677   drop_arrow = GetDropArrowImage(point_down);
1678 }
1679 
~DropInfo()1680 TabStripGtk::DropInfo::~DropInfo() {
1681   DestroyContainer();
1682 }
1683 
OnExposeEvent(GtkWidget * widget,GdkEventExpose * event)1684 gboolean TabStripGtk::DropInfo::OnExposeEvent(GtkWidget* widget,
1685                                               GdkEventExpose* event) {
1686   if (gtk_util::IsScreenComposited()) {
1687     SetContainerTransparency();
1688   } else {
1689     SetContainerShapeMask();
1690   }
1691 
1692   gdk_pixbuf_render_to_drawable(drop_arrow,
1693                                 container->window,
1694                                 0, 0, 0,
1695                                 0, 0,
1696                                 drop_indicator_width,
1697                                 drop_indicator_height,
1698                                 GDK_RGB_DITHER_NONE, 0, 0);
1699 
1700   return FALSE;
1701 }
1702 
1703 // Sets the color map of the container window to allow the window to be
1704 // transparent.
SetContainerColorMap()1705 void TabStripGtk::DropInfo::SetContainerColorMap() {
1706   GdkScreen* screen = gtk_widget_get_screen(container);
1707   GdkColormap* colormap = gdk_screen_get_rgba_colormap(screen);
1708 
1709   // If rgba is not available, use rgb instead.
1710   if (!colormap)
1711     colormap = gdk_screen_get_rgb_colormap(screen);
1712 
1713   gtk_widget_set_colormap(container, colormap);
1714 }
1715 
1716 // Sets full transparency for the container window.  This is used if
1717 // compositing is available for the screen.
SetContainerTransparency()1718 void TabStripGtk::DropInfo::SetContainerTransparency() {
1719   cairo_t* cairo_context = gdk_cairo_create(container->window);
1720   if (!cairo_context)
1721       return;
1722 
1723   // Make the background of the dragged tab window fully transparent.  All of
1724   // the content of the window (child widgets) will be completely opaque.
1725 
1726   cairo_scale(cairo_context, static_cast<double>(drop_indicator_width),
1727               static_cast<double>(drop_indicator_height));
1728   cairo_set_source_rgba(cairo_context, 1.0f, 1.0f, 1.0f, 0.0f);
1729   cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE);
1730   cairo_paint(cairo_context);
1731   cairo_destroy(cairo_context);
1732 }
1733 
1734 // Sets the shape mask for the container window to emulate a transparent
1735 // container window.  This is used if compositing is not available for the
1736 // screen.
SetContainerShapeMask()1737 void TabStripGtk::DropInfo::SetContainerShapeMask() {
1738   // Create a 1bpp bitmap the size of |container|.
1739   GdkPixmap* pixmap = gdk_pixmap_new(NULL,
1740                                      drop_indicator_width,
1741                                      drop_indicator_height, 1);
1742   cairo_t* cairo_context = gdk_cairo_create(GDK_DRAWABLE(pixmap));
1743 
1744   // Set the transparency.
1745   cairo_set_source_rgba(cairo_context, 1, 1, 1, 0);
1746 
1747   // Blit the rendered bitmap into a pixmap.  Any pixel set in the pixmap will
1748   // be opaque in the container window.
1749   cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE);
1750   gdk_cairo_set_source_pixbuf(cairo_context, drop_arrow, 0, 0);
1751   cairo_paint(cairo_context);
1752   cairo_destroy(cairo_context);
1753 
1754   // Set the shape mask.
1755   gdk_window_shape_combine_mask(container->window, pixmap, 0, 0);
1756   g_object_unref(pixmap);
1757 }
1758 
CreateContainer()1759 void TabStripGtk::DropInfo::CreateContainer() {
1760   container = gtk_window_new(GTK_WINDOW_POPUP);
1761   SetContainerColorMap();
1762   gtk_widget_set_app_paintable(container, TRUE);
1763   g_signal_connect(container, "expose-event",
1764                    G_CALLBACK(OnExposeEventThunk), this);
1765   gtk_widget_add_events(container, GDK_STRUCTURE_MASK);
1766   gtk_window_move(GTK_WINDOW(container), 0, 0);
1767   gtk_window_resize(GTK_WINDOW(container),
1768                     drop_indicator_width, drop_indicator_height);
1769   gtk_widget_show_all(container);
1770 }
1771 
DestroyContainer()1772 void TabStripGtk::DropInfo::DestroyContainer() {
1773   if (GTK_IS_WIDGET(container))
1774     gtk_widget_destroy(container);
1775 }
1776 
StopAnimation()1777 void TabStripGtk::StopAnimation() {
1778   if (active_animation_.get())
1779     active_animation_->Stop();
1780 }
1781 
1782 // Called from:
1783 // - animation tick
AnimationLayout(double unselected_width)1784 void TabStripGtk::AnimationLayout(double unselected_width) {
1785   int tab_height = TabGtk::GetStandardSize().height();
1786   double tab_x = tab_start_x();
1787   for (int i = 0; i < GetTabCount(); ++i) {
1788     TabAnimation* animation = active_animation_.get();
1789     if (animation)
1790       tab_x += animation->GetGapWidth(i);
1791     double tab_width = TabAnimation::GetCurrentTabWidth(this, animation, i);
1792     double end_of_tab = tab_x + tab_width;
1793     int rounded_tab_x = Round(tab_x);
1794     TabGtk* tab = GetTabAt(i);
1795     gfx::Rect bounds(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
1796                      tab_height);
1797     SetTabBounds(tab, bounds);
1798     tab_x = end_of_tab + GetTabHOffset(i + 1);
1799   }
1800   LayoutNewTabButton(tab_x, unselected_width);
1801 }
1802 
StartInsertTabAnimation(int index)1803 void TabStripGtk::StartInsertTabAnimation(int index) {
1804   // The TabStrip can now use its entire width to lay out Tabs.
1805   available_width_for_tabs_ = -1;
1806   StopAnimation();
1807   active_animation_.reset(new InsertTabAnimation(this, index));
1808   active_animation_->Start();
1809 }
1810 
StartRemoveTabAnimation(int index,TabContents * contents)1811 void TabStripGtk::StartRemoveTabAnimation(int index, TabContents* contents) {
1812   if (active_animation_.get()) {
1813     // Some animations (e.g. MoveTabAnimation) cause there to be a Layout when
1814     // they're completed (which includes canceled). Since |tab_data_| is now
1815     // inconsistent with TabStripModel, doing this Layout will crash now, so
1816     // we ask the MoveTabAnimation to skip its Layout (the state will be
1817     // corrected by the RemoveTabAnimation we're about to initiate).
1818     active_animation_->set_layout_on_completion(false);
1819     active_animation_->Stop();
1820   }
1821 
1822   active_animation_.reset(new RemoveTabAnimation(this, index, contents));
1823   active_animation_->Start();
1824 }
1825 
StartMoveTabAnimation(int from_index,int to_index)1826 void TabStripGtk::StartMoveTabAnimation(int from_index, int to_index) {
1827   StopAnimation();
1828   active_animation_.reset(new MoveTabAnimation(this, from_index, to_index));
1829   active_animation_->Start();
1830 }
1831 
StartResizeLayoutAnimation()1832 void TabStripGtk::StartResizeLayoutAnimation() {
1833   StopAnimation();
1834   active_animation_.reset(new ResizeLayoutAnimation(this));
1835   active_animation_->Start();
1836 }
1837 
StartMiniTabAnimation(int index)1838 void TabStripGtk::StartMiniTabAnimation(int index) {
1839   StopAnimation();
1840   active_animation_.reset(new MiniTabAnimation(this, index));
1841   active_animation_->Start();
1842 }
1843 
StartMiniMoveTabAnimation(int from_index,int to_index,const gfx::Rect & start_bounds)1844 void TabStripGtk::StartMiniMoveTabAnimation(int from_index,
1845                                             int to_index,
1846                                             const gfx::Rect& start_bounds) {
1847   StopAnimation();
1848   active_animation_.reset(
1849       new MiniMoveAnimation(this, from_index, to_index, start_bounds));
1850   active_animation_->Start();
1851 }
1852 
FinishAnimation(TabStripGtk::TabAnimation * animation,bool layout)1853 void TabStripGtk::FinishAnimation(TabStripGtk::TabAnimation* animation,
1854                                   bool layout) {
1855   active_animation_.reset(NULL);
1856 
1857   // Reset the animation state of each tab.
1858   for (int i = 0, count = GetTabCount(); i < count; ++i)
1859     GetTabAt(i)->set_animating_mini_change(false);
1860 
1861   if (layout)
1862     Layout();
1863 }
1864 
OnExpose(GtkWidget * widget,GdkEventExpose * event)1865 gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event) {
1866   if (gdk_region_empty(event->region))
1867     return TRUE;
1868 
1869   // If we're only repainting favicons, optimize the paint path and only draw
1870   // the favicons.
1871   GdkRectangle* rects;
1872   gint num_rects;
1873   gdk_region_get_rectangles(event->region, &rects, &num_rects);
1874   qsort(rects, num_rects, sizeof(GdkRectangle), CompareGdkRectangles);
1875   std::vector<int> tabs_to_repaint;
1876   if (!IsDragSessionActive() &&
1877       CanPaintOnlyFavicons(rects, num_rects, &tabs_to_repaint)) {
1878     PaintOnlyFavicons(event, tabs_to_repaint);
1879     g_free(rects);
1880     return TRUE;
1881   }
1882   g_free(rects);
1883 
1884   // TODO(jhawkins): Ideally we'd like to only draw what's needed in the damage
1885   // rect, but the tab widgets overlap each other, and painting on one widget
1886   // will cause an expose-event to be sent to the widgets underneath.  The
1887   // underlying widget does not need to be redrawn as we control the order of
1888   // expose-events.  Currently we hack it to redraw the entire tabstrip.  We
1889   // could change the damage rect to just contain the tabs + the new tab button.
1890   event->area.x = 0;
1891   event->area.y = 0;
1892   event->area.width = bounds_.width();
1893   event->area.height = bounds_.height();
1894   gdk_region_union_with_rect(event->region, &event->area);
1895 
1896   // Paint the New Tab button.
1897   gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()),
1898       newtab_button_->widget(), event);
1899 
1900   // Paint the tabs in reverse order, so they stack to the left.
1901   TabGtk* selected_tab = NULL;
1902   int tab_count = GetTabCount();
1903   for (int i = tab_count - 1; i >= 0; --i) {
1904     TabGtk* tab = GetTabAt(i);
1905     // We must ask the _Tab's_ model, not ourselves, because in some situations
1906     // the model will be different to this object, e.g. when a Tab is being
1907     // removed after its TabContents has been destroyed.
1908     if (!tab->IsSelected()) {
1909       gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()),
1910                                      tab->widget(), event);
1911     } else {
1912       selected_tab = tab;
1913     }
1914   }
1915 
1916   // Paint the selected tab last, so it overlaps all the others.
1917   if (selected_tab) {
1918     gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()),
1919                                    selected_tab->widget(), event);
1920   }
1921 
1922   return TRUE;
1923 }
1924 
OnSizeAllocate(GtkWidget * widget,GtkAllocation * allocation)1925 void TabStripGtk::OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) {
1926   gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y,
1927       allocation->width, allocation->height);
1928 
1929   // Nothing to do if the bounds are the same.  If we don't catch this, we'll
1930   // get an infinite loop of size-allocate signals.
1931   if (bounds_ == bounds)
1932     return;
1933 
1934   SetBounds(bounds);
1935 
1936   // No tabs, nothing to layout.  This happens when a browser window is created
1937   // and shown before tabs are added (as in a popup window).
1938   if (GetTabCount() == 0)
1939     return;
1940 
1941   // When there is only one tab, Layout() so we don't animate it. With more
1942   // tabs, do ResizeLayoutTabs(). In RTL(), we will also need to manually
1943   // Layout() when ResizeLayoutTabs() is a no-op.
1944   if ((GetTabCount() == 1) || (!ResizeLayoutTabs() && base::i18n::IsRTL()))
1945     Layout();
1946 }
1947 
OnDragMotion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time)1948 gboolean TabStripGtk::OnDragMotion(GtkWidget* widget, GdkDragContext* context,
1949                                    gint x, gint y, guint time) {
1950   UpdateDropIndex(context, x, y);
1951   return TRUE;
1952 }
1953 
OnDragDrop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time)1954 gboolean TabStripGtk::OnDragDrop(GtkWidget* widget, GdkDragContext* context,
1955                                  gint x, gint y, guint time) {
1956   if (!drop_info_.get())
1957     return FALSE;
1958 
1959   GdkAtom target = gtk_drag_dest_find_target(widget, context, NULL);
1960   if (target != GDK_NONE)
1961     gtk_drag_finish(context, FALSE, FALSE, time);
1962   else
1963     gtk_drag_get_data(widget, context, target, time);
1964 
1965   return TRUE;
1966 }
1967 
OnDragLeave(GtkWidget * widget,GdkDragContext * context,guint time)1968 gboolean TabStripGtk::OnDragLeave(GtkWidget* widget, GdkDragContext* context,
1969                                   guint time) {
1970   // Destroy the drop indicator.
1971   drop_info_->DestroyContainer();
1972   return FALSE;
1973 }
1974 
OnDragDataReceived(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * data,guint info,guint time)1975 gboolean TabStripGtk::OnDragDataReceived(GtkWidget* widget,
1976                                          GdkDragContext* context,
1977                                          gint x, gint y,
1978                                          GtkSelectionData* data,
1979                                          guint info, guint time) {
1980   bool success = false;
1981 
1982   if (info == ui::TEXT_URI_LIST ||
1983       info == ui::NETSCAPE_URL ||
1984       info == ui::TEXT_PLAIN) {
1985     success = CompleteDrop(data->data, info == ui::TEXT_PLAIN);
1986   }
1987 
1988   gtk_drag_finish(context, success, success, time);
1989   return TRUE;
1990 }
1991 
OnNewTabClicked(GtkWidget * widget)1992 void TabStripGtk::OnNewTabClicked(GtkWidget* widget) {
1993   GdkEvent* event = gtk_get_current_event();
1994   DCHECK_EQ(event->type, GDK_BUTTON_RELEASE);
1995   int mouse_button = event->button.button;
1996   gdk_event_free(event);
1997 
1998   switch (mouse_button) {
1999     case 1:
2000       model_->delegate()->AddBlankTab(true);
2001       break;
2002     case 2: {
2003       // On middle-click, try to parse the PRIMARY selection as a URL and load
2004       // it instead of creating a blank page.
2005       GURL url;
2006       if (!gtk_util::URLFromPrimarySelection(model_->profile(), &url))
2007         return;
2008 
2009       Browser* browser = window_->browser();
2010       DCHECK(browser);
2011       browser->AddSelectedTabWithURL(url, PageTransition::TYPED);
2012       break;
2013     }
2014     default:
2015       NOTREACHED() << "Got click on new tab button with unhandled mouse "
2016                    << "button " << mouse_button;
2017   }
2018 }
2019 
SetTabBounds(TabGtk * tab,const gfx::Rect & bounds)2020 void TabStripGtk::SetTabBounds(TabGtk* tab, const gfx::Rect& bounds) {
2021   gfx::Rect bds = bounds;
2022   bds.set_x(gtk_util::MirroredLeftPointForRect(tabstrip_.get(), bounds));
2023   tab->SetBounds(bds);
2024   gtk_fixed_move(GTK_FIXED(tabstrip_.get()), tab->widget(),
2025                  bds.x(), bds.y());
2026 }
2027 
CanPaintOnlyFavicons(const GdkRectangle * rects,int num_rects,std::vector<int> * tabs_to_paint)2028 bool TabStripGtk::CanPaintOnlyFavicons(const GdkRectangle* rects,
2029     int num_rects, std::vector<int>* tabs_to_paint) {
2030   // |rects| are sorted so we just need to scan from left to right and compare
2031   // it to the tab favicon positions from left to right.
2032   int t = 0;
2033   for (int r = 0; r < num_rects; ++r) {
2034     while (t < GetTabCount()) {
2035       TabGtk* tab = GetTabAt(t);
2036       if (GdkRectMatchesTabFaviconBounds(rects[r], tab) &&
2037           tab->ShouldShowIcon()) {
2038         tabs_to_paint->push_back(t);
2039         ++t;
2040         break;
2041       }
2042       ++t;
2043     }
2044   }
2045   return static_cast<int>(tabs_to_paint->size()) == num_rects;
2046 }
2047 
PaintOnlyFavicons(GdkEventExpose * event,const std::vector<int> & tabs_to_paint)2048 void TabStripGtk::PaintOnlyFavicons(GdkEventExpose* event,
2049                                     const std::vector<int>& tabs_to_paint) {
2050   for (size_t i = 0; i < tabs_to_paint.size(); ++i)
2051     GetTabAt(tabs_to_paint[i])->PaintFaviconArea(event);
2052 }
2053 
MakeNewTabButton()2054 CustomDrawButton* TabStripGtk::MakeNewTabButton() {
2055   CustomDrawButton* button = new CustomDrawButton(IDR_NEWTAB_BUTTON,
2056       IDR_NEWTAB_BUTTON_P, IDR_NEWTAB_BUTTON_H, 0);
2057 
2058   // Let the middle mouse button initiate clicks as well.
2059   gtk_util::SetButtonTriggersNavigation(button->widget());
2060   g_signal_connect(button->widget(), "clicked",
2061                    G_CALLBACK(OnNewTabClickedThunk), this);
2062   GTK_WIDGET_UNSET_FLAGS(button->widget(), GTK_CAN_FOCUS);
2063   gtk_fixed_put(GTK_FIXED(tabstrip_.get()), button->widget(), 0, 0);
2064 
2065   return button;
2066 }
2067