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