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(¶ms);
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