Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(532)

Side by Side Diff: chrome/browser/ui/gtk/tabs/tab_strip_gtk.cc

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

Powered by Google App Engine
This is Rietveld 408576698