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