| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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/gtk/tabs/tab_strip_gtk.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "app/gtk_dnd_util.h" | |
| 10 #include "app/resource_bundle.h" | |
| 11 #include "base/i18n/rtl.h" | |
| 12 #include "base/string_util.h" | |
| 13 #include "base/utf_string_conversions.h" | |
| 14 #include "chrome/browser/autocomplete/autocomplete.h" | |
| 15 #include "chrome/browser/autocomplete/autocomplete_classifier.h" | |
| 16 #include "chrome/browser/autocomplete/autocomplete_match.h" | |
| 17 #include "chrome/browser/gtk/browser_window_gtk.h" | |
| 18 #include "chrome/browser/gtk/custom_button.h" | |
| 19 #include "chrome/browser/gtk/gtk_theme_provider.h" | |
| 20 #include "chrome/browser/gtk/gtk_util.h" | |
| 21 #include "chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h" | |
| 22 #include "chrome/browser/profiles/profile.h" | |
| 23 #include "chrome/browser/tab_contents/tab_contents.h" | |
| 24 #include "chrome/browser/tabs/tab_strip_model_delegate.h" | |
| 25 #include "chrome/browser/themes/browser_theme_provider.h" | |
| 26 #include "chrome/browser/ui/browser.h" | |
| 27 #include "chrome/browser/ui/browser_navigator.h" | |
| 28 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | |
| 29 #include "chrome/common/notification_service.h" | |
| 30 #include "chrome/common/notification_type.h" | |
| 31 #include "gfx/gtk_util.h" | |
| 32 #include "gfx/point.h" | |
| 33 #include "grit/app_resources.h" | |
| 34 #include "grit/theme_resources.h" | |
| 35 #include "ui/base/animation/animation_delegate.h" | |
| 36 #include "ui/base/animation/slide_animation.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()->selected_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_provider_(GtkThemeProvider::GetFrom(model->profile())), | |
| 701 resize_layout_factory_(this), | |
| 702 added_as_message_loop_observer_(false) { | |
| 703 theme_provider_->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[] = { gtk_dnd_util::TEXT_URI_LIST, | |
| 742 gtk_dnd_util::NETSCAPE_URL, | |
| 743 gtk_dnd_util::TEXT_PLAIN, | |
| 744 -1 }; | |
| 745 gtk_dnd_util::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_->selected_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_->SelectTabContentsAt(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 ThemeProvider* TabStripGtk::GetThemeProvider() { | |
| 1210 return theme_provider_; | |
| 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 void TabStripGtk::Observe(NotificationType type, | |
| 1232 const NotificationSource& source, | |
| 1233 const NotificationDetails& details) { | |
| 1234 if (type == NotificationType::BROWSER_THEME_CHANGED) { | |
| 1235 TabRendererGtk::SetSelectedTitleColor(theme_provider_->GetColor( | |
| 1236 BrowserThemeProvider::COLOR_TAB_TEXT)); | |
| 1237 TabRendererGtk::SetUnselectedTitleColor(theme_provider_->GetColor( | |
| 1238 BrowserThemeProvider::COLOR_BACKGROUND_TAB_TEXT)); | |
| 1239 } | |
| 1240 } | |
| 1241 | |
| 1242 //////////////////////////////////////////////////////////////////////////////// | |
| 1243 // TabStripGtk, private: | |
| 1244 | |
| 1245 int TabStripGtk::GetTabCount() const { | |
| 1246 return static_cast<int>(tab_data_.size()); | |
| 1247 } | |
| 1248 | |
| 1249 int TabStripGtk::GetMiniTabCount() const { | |
| 1250 int mini_count = 0; | |
| 1251 for (size_t i = 0; i < tab_data_.size(); ++i) { | |
| 1252 if (tab_data_[i].tab->mini()) | |
| 1253 mini_count++; | |
| 1254 else | |
| 1255 return mini_count; | |
| 1256 } | |
| 1257 return mini_count; | |
| 1258 } | |
| 1259 | |
| 1260 int TabStripGtk::GetAvailableWidthForTabs(TabGtk* last_tab) const { | |
| 1261 if (!base::i18n::IsRTL()) | |
| 1262 return last_tab->x() - bounds_.x() + last_tab->width(); | |
| 1263 else | |
| 1264 return bounds_.width() - last_tab->x(); | |
| 1265 } | |
| 1266 | |
| 1267 int TabStripGtk::GetIndexOfTab(const TabGtk* tab) const { | |
| 1268 for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) { | |
| 1269 TabGtk* current_tab = GetTabAt(i); | |
| 1270 if (current_tab->closing()) { | |
| 1271 --index; | |
| 1272 } else if (current_tab == tab) { | |
| 1273 return index; | |
| 1274 } | |
| 1275 } | |
| 1276 return -1; | |
| 1277 } | |
| 1278 | |
| 1279 TabGtk* TabStripGtk::GetTabAt(int index) const { | |
| 1280 DCHECK_GE(index, 0); | |
| 1281 DCHECK_LT(index, GetTabCount()); | |
| 1282 return tab_data_.at(index).tab; | |
| 1283 } | |
| 1284 | |
| 1285 TabGtk* TabStripGtk::GetTabAtAdjustForAnimation(int index) const { | |
| 1286 if (active_animation_.get() && | |
| 1287 active_animation_->type() == TabAnimation::REMOVE && | |
| 1288 index >= | |
| 1289 static_cast<RemoveTabAnimation*>(active_animation_.get())->index()) { | |
| 1290 index++; | |
| 1291 } | |
| 1292 return GetTabAt(index); | |
| 1293 } | |
| 1294 | |
| 1295 void TabStripGtk::RemoveTabAt(int index) { | |
| 1296 TabGtk* removed = tab_data_.at(index).tab; | |
| 1297 | |
| 1298 // Remove the Tab from the TabStrip's list. | |
| 1299 tab_data_.erase(tab_data_.begin() + index); | |
| 1300 | |
| 1301 if (!IsDragSessionActive() || !drag_controller_->IsDragSourceTab(removed)) { | |
| 1302 gtk_container_remove(GTK_CONTAINER(tabstrip_.get()), removed->widget()); | |
| 1303 delete removed; | |
| 1304 } | |
| 1305 } | |
| 1306 | |
| 1307 void TabStripGtk::HandleGlobalMouseMoveEvent() { | |
| 1308 if (!IsCursorInTabStripZone()) { | |
| 1309 // Mouse moved outside the tab slop zone, start a timer to do a resize | |
| 1310 // layout after a short while... | |
| 1311 if (resize_layout_factory_.empty()) { | |
| 1312 MessageLoop::current()->PostDelayedTask(FROM_HERE, | |
| 1313 resize_layout_factory_.NewRunnableMethod( | |
| 1314 &TabStripGtk::ResizeLayoutTabs), | |
| 1315 kResizeTabsTimeMs); | |
| 1316 } | |
| 1317 } else { | |
| 1318 // Mouse moved quickly out of the tab strip and then into it again, so | |
| 1319 // cancel the timer so that the strip doesn't move when the mouse moves | |
| 1320 // back over it. | |
| 1321 resize_layout_factory_.RevokeAll(); | |
| 1322 } | |
| 1323 } | |
| 1324 | |
| 1325 void TabStripGtk::GenerateIdealBounds() { | |
| 1326 int tab_count = GetTabCount(); | |
| 1327 double unselected, selected; | |
| 1328 GetDesiredTabWidths(tab_count, GetMiniTabCount(), &unselected, &selected); | |
| 1329 | |
| 1330 current_unselected_width_ = unselected; | |
| 1331 current_selected_width_ = selected; | |
| 1332 | |
| 1333 // NOTE: This currently assumes a tab's height doesn't differ based on | |
| 1334 // selected state or the number of tabs in the strip! | |
| 1335 int tab_height = TabGtk::GetStandardSize().height(); | |
| 1336 double tab_x = tab_start_x(); | |
| 1337 for (int i = 0; i < tab_count; ++i) { | |
| 1338 TabGtk* tab = GetTabAt(i); | |
| 1339 double tab_width = unselected; | |
| 1340 if (tab->mini()) | |
| 1341 tab_width = TabGtk::GetMiniWidth(); | |
| 1342 else if (tab->IsSelected()) | |
| 1343 tab_width = selected; | |
| 1344 double end_of_tab = tab_x + tab_width; | |
| 1345 int rounded_tab_x = Round(tab_x); | |
| 1346 gfx::Rect state(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, | |
| 1347 tab_height); | |
| 1348 tab_data_.at(i).ideal_bounds = state; | |
| 1349 tab_x = end_of_tab + GetTabHOffset(i + 1); | |
| 1350 } | |
| 1351 } | |
| 1352 | |
| 1353 void TabStripGtk::LayoutNewTabButton(double last_tab_right, | |
| 1354 double unselected_width) { | |
| 1355 gfx::Rect bounds(0, kNewTabButtonVOffset, | |
| 1356 newtab_button_->width(), newtab_button_->height()); | |
| 1357 int delta = abs(Round(unselected_width) - TabGtk::GetStandardSize().width()); | |
| 1358 if (delta > 1 && !needs_resize_layout_) { | |
| 1359 // We're shrinking tabs, so we need to anchor the New Tab button to the | |
| 1360 // right edge of the TabStrip's bounds, rather than the right edge of the | |
| 1361 // right-most Tab, otherwise it'll bounce when animating. | |
| 1362 bounds.set_x(bounds_.width() - newtab_button_->width()); | |
| 1363 } else { | |
| 1364 bounds.set_x(Round(last_tab_right - kTabHOffset) + kNewTabButtonHOffset); | |
| 1365 } | |
| 1366 bounds.set_x(gtk_util::MirroredLeftPointForRect(tabstrip_.get(), bounds)); | |
| 1367 | |
| 1368 gtk_fixed_move(GTK_FIXED(tabstrip_.get()), newtab_button_->widget(), | |
| 1369 bounds.x(), bounds.y()); | |
| 1370 } | |
| 1371 | |
| 1372 void TabStripGtk::GetDesiredTabWidths(int tab_count, | |
| 1373 int mini_tab_count, | |
| 1374 double* unselected_width, | |
| 1375 double* selected_width) const { | |
| 1376 DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count); | |
| 1377 const double min_unselected_width = | |
| 1378 TabGtk::GetMinimumUnselectedSize().width(); | |
| 1379 const double min_selected_width = | |
| 1380 TabGtk::GetMinimumSelectedSize().width(); | |
| 1381 | |
| 1382 *unselected_width = min_unselected_width; | |
| 1383 *selected_width = min_selected_width; | |
| 1384 | |
| 1385 if (tab_count == 0) { | |
| 1386 // Return immediately to avoid divide-by-zero below. | |
| 1387 return; | |
| 1388 } | |
| 1389 | |
| 1390 // Determine how much space we can actually allocate to tabs. | |
| 1391 int available_width = tabstrip_->allocation.width; | |
| 1392 if (available_width_for_tabs_ < 0) { | |
| 1393 available_width = bounds_.width(); | |
| 1394 available_width -= | |
| 1395 (kNewTabButtonHOffset + newtab_button_->width()); | |
| 1396 } else { | |
| 1397 // Interesting corner case: if |available_width_for_tabs_| > the result | |
| 1398 // of the calculation in the conditional arm above, the strip is in | |
| 1399 // overflow. We can either use the specified width or the true available | |
| 1400 // width here; the first preserves the consistent "leave the last tab under | |
| 1401 // the user's mouse so they can close many tabs" behavior at the cost of | |
| 1402 // prolonging the glitchy appearance of the overflow state, while the second | |
| 1403 // gets us out of overflow as soon as possible but forces the user to move | |
| 1404 // their mouse for a few tabs' worth of closing. We choose visual | |
| 1405 // imperfection over behavioral imperfection and select the first option. | |
| 1406 available_width = available_width_for_tabs_; | |
| 1407 } | |
| 1408 | |
| 1409 if (mini_tab_count > 0) { | |
| 1410 available_width -= mini_tab_count * (TabGtk::GetMiniWidth() + kTabHOffset); | |
| 1411 tab_count -= mini_tab_count; | |
| 1412 if (tab_count == 0) { | |
| 1413 *selected_width = *unselected_width = TabGtk::GetStandardSize().width(); | |
| 1414 return; | |
| 1415 } | |
| 1416 // Account for gap between the last mini-tab and first normal tab. | |
| 1417 available_width -= mini_to_non_mini_gap_; | |
| 1418 } | |
| 1419 | |
| 1420 // Calculate the desired tab widths by dividing the available space into equal | |
| 1421 // portions. Don't let tabs get larger than the "standard width" or smaller | |
| 1422 // than the minimum width for each type, respectively. | |
| 1423 const int total_offset = kTabHOffset * (tab_count - 1); | |
| 1424 const double desired_tab_width = std::min( | |
| 1425 (static_cast<double>(available_width - total_offset) / | |
| 1426 static_cast<double>(tab_count)), | |
| 1427 static_cast<double>(TabGtk::GetStandardSize().width())); | |
| 1428 | |
| 1429 *unselected_width = std::max(desired_tab_width, min_unselected_width); | |
| 1430 *selected_width = std::max(desired_tab_width, min_selected_width); | |
| 1431 | |
| 1432 // When there are multiple tabs, we'll have one selected and some unselected | |
| 1433 // tabs. If the desired width was between the minimum sizes of these types, | |
| 1434 // try to shrink the tabs with the smaller minimum. For example, if we have | |
| 1435 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If | |
| 1436 // selected tabs have a minimum width of 4 and unselected tabs have a minimum | |
| 1437 // width of 1, the above code would set *unselected_width = 2.5, | |
| 1438 // *selected_width = 4, which results in a total width of 11.5. Instead, we | |
| 1439 // want to set *unselected_width = 2, *selected_width = 4, for a total width | |
| 1440 // of 10. | |
| 1441 if (tab_count > 1) { | |
| 1442 if ((min_unselected_width < min_selected_width) && | |
| 1443 (desired_tab_width < min_selected_width)) { | |
| 1444 double calc_width = | |
| 1445 static_cast<double>( | |
| 1446 available_width - total_offset - min_selected_width) / | |
| 1447 static_cast<double>(tab_count - 1); | |
| 1448 *unselected_width = std::max(calc_width, min_unselected_width); | |
| 1449 } else if ((min_unselected_width > min_selected_width) && | |
| 1450 (desired_tab_width < min_unselected_width)) { | |
| 1451 *selected_width = std::max(available_width - total_offset - | |
| 1452 (min_unselected_width * (tab_count - 1)), min_selected_width); | |
| 1453 } | |
| 1454 } | |
| 1455 } | |
| 1456 | |
| 1457 int TabStripGtk::GetTabHOffset(int tab_index) { | |
| 1458 if (tab_index < GetTabCount() && GetTabAt(tab_index - 1)->mini() && | |
| 1459 !GetTabAt(tab_index)->mini()) { | |
| 1460 return mini_to_non_mini_gap_ + kTabHOffset; | |
| 1461 } | |
| 1462 return kTabHOffset; | |
| 1463 } | |
| 1464 | |
| 1465 int TabStripGtk::tab_start_x() const { | |
| 1466 return 0; | |
| 1467 } | |
| 1468 | |
| 1469 bool TabStripGtk::ResizeLayoutTabs() { | |
| 1470 resize_layout_factory_.RevokeAll(); | |
| 1471 | |
| 1472 // It is critically important that this is unhooked here, otherwise we will | |
| 1473 // keep spying on messages forever. | |
| 1474 RemoveMessageLoopObserver(); | |
| 1475 | |
| 1476 available_width_for_tabs_ = -1; | |
| 1477 int mini_tab_count = GetMiniTabCount(); | |
| 1478 if (mini_tab_count == GetTabCount()) { | |
| 1479 // Only mini tabs, we know the tab widths won't have changed (all mini-tabs | |
| 1480 // have the same width), so there is nothing to do. | |
| 1481 return false; | |
| 1482 } | |
| 1483 TabGtk* first_tab = GetTabAt(mini_tab_count); | |
| 1484 double unselected, selected; | |
| 1485 GetDesiredTabWidths(GetTabCount(), mini_tab_count, &unselected, &selected); | |
| 1486 int w = Round(first_tab->IsSelected() ? selected : unselected); | |
| 1487 | |
| 1488 // We only want to run the animation if we're not already at the desired | |
| 1489 // size. | |
| 1490 if (abs(first_tab->width() - w) > 1) { | |
| 1491 StartResizeLayoutAnimation(); | |
| 1492 return true; | |
| 1493 } | |
| 1494 | |
| 1495 return false; | |
| 1496 } | |
| 1497 | |
| 1498 bool TabStripGtk::IsCursorInTabStripZone() const { | |
| 1499 gfx::Point tabstrip_topleft; | |
| 1500 gtk_util::ConvertWidgetPointToScreen(tabstrip_.get(), &tabstrip_topleft); | |
| 1501 | |
| 1502 gfx::Rect bds = bounds(); | |
| 1503 bds.set_origin(tabstrip_topleft); | |
| 1504 bds.set_height(bds.height() + kTabStripAnimationVSlop); | |
| 1505 | |
| 1506 GdkScreen* screen = gdk_screen_get_default(); | |
| 1507 GdkDisplay* display = gdk_screen_get_display(screen); | |
| 1508 gint x, y; | |
| 1509 gdk_display_get_pointer(display, NULL, &x, &y, NULL); | |
| 1510 gfx::Point cursor_point(x, y); | |
| 1511 | |
| 1512 return bds.Contains(cursor_point); | |
| 1513 } | |
| 1514 | |
| 1515 void TabStripGtk::AddMessageLoopObserver() { | |
| 1516 if (!added_as_message_loop_observer_) { | |
| 1517 MessageLoopForUI::current()->AddObserver(this); | |
| 1518 added_as_message_loop_observer_ = true; | |
| 1519 } | |
| 1520 } | |
| 1521 | |
| 1522 void TabStripGtk::RemoveMessageLoopObserver() { | |
| 1523 if (added_as_message_loop_observer_) { | |
| 1524 MessageLoopForUI::current()->RemoveObserver(this); | |
| 1525 added_as_message_loop_observer_ = false; | |
| 1526 } | |
| 1527 } | |
| 1528 | |
| 1529 gfx::Rect TabStripGtk::GetDropBounds(int drop_index, | |
| 1530 bool drop_before, | |
| 1531 bool* is_beneath) { | |
| 1532 DCHECK_NE(drop_index, -1); | |
| 1533 int center_x; | |
| 1534 if (drop_index < GetTabCount()) { | |
| 1535 TabGtk* tab = GetTabAt(drop_index); | |
| 1536 gfx::Rect bounds = tab->GetNonMirroredBounds(tabstrip_.get()); | |
| 1537 // TODO(sky): update these for pinned tabs. | |
| 1538 if (drop_before) | |
| 1539 center_x = bounds.x() - (kTabHOffset / 2); | |
| 1540 else | |
| 1541 center_x = bounds.x() + (bounds.width() / 2); | |
| 1542 } else { | |
| 1543 TabGtk* last_tab = GetTabAt(drop_index - 1); | |
| 1544 gfx::Rect bounds = last_tab->GetNonMirroredBounds(tabstrip_.get()); | |
| 1545 center_x = bounds.x() + bounds.width() + (kTabHOffset / 2); | |
| 1546 } | |
| 1547 | |
| 1548 center_x = gtk_util::MirroredXCoordinate(tabstrip_.get(), center_x); | |
| 1549 | |
| 1550 // Determine the screen bounds. | |
| 1551 gfx::Point drop_loc(center_x - drop_indicator_width / 2, | |
| 1552 -drop_indicator_height); | |
| 1553 gtk_util::ConvertWidgetPointToScreen(tabstrip_.get(), &drop_loc); | |
| 1554 gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width, | |
| 1555 drop_indicator_height); | |
| 1556 | |
| 1557 // TODO(jhawkins): We always display the arrow underneath the tab because we | |
| 1558 // don't have custom frame support yet. | |
| 1559 *is_beneath = true; | |
| 1560 if (*is_beneath) | |
| 1561 drop_bounds.Offset(0, drop_bounds.height() + bounds().height()); | |
| 1562 | |
| 1563 return drop_bounds; | |
| 1564 } | |
| 1565 | |
| 1566 void TabStripGtk::UpdateDropIndex(GdkDragContext* context, gint x, gint y) { | |
| 1567 // If the UI layout is right-to-left, we need to mirror the mouse | |
| 1568 // coordinates since we calculate the drop index based on the | |
| 1569 // original (and therefore non-mirrored) positions of the tabs. | |
| 1570 x = gtk_util::MirroredXCoordinate(tabstrip_.get(), x); | |
| 1571 // We don't allow replacing the urls of mini-tabs. | |
| 1572 for (int i = GetMiniTabCount(); i < GetTabCount(); ++i) { | |
| 1573 TabGtk* tab = GetTabAt(i); | |
| 1574 gfx::Rect bounds = tab->GetNonMirroredBounds(tabstrip_.get()); | |
| 1575 const int tab_max_x = bounds.x() + bounds.width(); | |
| 1576 const int hot_width = bounds.width() / 3; | |
| 1577 if (x < tab_max_x) { | |
| 1578 if (x < bounds.x() + hot_width) | |
| 1579 SetDropIndex(i, true); | |
| 1580 else if (x >= tab_max_x - hot_width) | |
| 1581 SetDropIndex(i + 1, true); | |
| 1582 else | |
| 1583 SetDropIndex(i, false); | |
| 1584 return; | |
| 1585 } | |
| 1586 } | |
| 1587 | |
| 1588 // The drop isn't over a tab, add it to the end. | |
| 1589 SetDropIndex(GetTabCount(), true); | |
| 1590 } | |
| 1591 | |
| 1592 void TabStripGtk::SetDropIndex(int index, bool drop_before) { | |
| 1593 bool is_beneath; | |
| 1594 gfx::Rect drop_bounds = GetDropBounds(index, drop_before, &is_beneath); | |
| 1595 | |
| 1596 if (!drop_info_.get()) { | |
| 1597 drop_info_.reset(new DropInfo(index, drop_before, !is_beneath)); | |
| 1598 } else { | |
| 1599 if (!GTK_IS_WIDGET(drop_info_->container)) { | |
| 1600 drop_info_->CreateContainer(); | |
| 1601 } else if (drop_info_->drop_index == index && | |
| 1602 drop_info_->drop_before == drop_before) { | |
| 1603 return; | |
| 1604 } | |
| 1605 | |
| 1606 drop_info_->drop_index = index; | |
| 1607 drop_info_->drop_before = drop_before; | |
| 1608 if (is_beneath == drop_info_->point_down) { | |
| 1609 drop_info_->point_down = !is_beneath; | |
| 1610 drop_info_->drop_arrow= GetDropArrowImage(drop_info_->point_down); | |
| 1611 } | |
| 1612 } | |
| 1613 | |
| 1614 gtk_window_move(GTK_WINDOW(drop_info_->container), | |
| 1615 drop_bounds.x(), drop_bounds.y()); | |
| 1616 gtk_window_resize(GTK_WINDOW(drop_info_->container), | |
| 1617 drop_bounds.width(), drop_bounds.height()); | |
| 1618 } | |
| 1619 | |
| 1620 bool TabStripGtk::CompleteDrop(guchar* data, bool is_plain_text) { | |
| 1621 if (!drop_info_.get()) | |
| 1622 return false; | |
| 1623 | |
| 1624 const int drop_index = drop_info_->drop_index; | |
| 1625 const bool drop_before = drop_info_->drop_before; | |
| 1626 | |
| 1627 // Destroy the drop indicator. | |
| 1628 drop_info_.reset(); | |
| 1629 | |
| 1630 GURL url; | |
| 1631 if (is_plain_text) { | |
| 1632 AutocompleteMatch match; | |
| 1633 model_->profile()->GetAutocompleteClassifier()->Classify( | |
| 1634 UTF8ToWide(reinterpret_cast<char*>(data)), std::wstring(), false, | |
| 1635 &match, NULL); | |
| 1636 url = match.destination_url; | |
| 1637 } else { | |
| 1638 std::string url_string(reinterpret_cast<char*>(data)); | |
| 1639 url = GURL(url_string.substr(0, url_string.find_first_of('\n'))); | |
| 1640 } | |
| 1641 if (!url.is_valid()) | |
| 1642 return false; | |
| 1643 | |
| 1644 browser::NavigateParams params(window()->browser(), url, | |
| 1645 PageTransition::LINK); | |
| 1646 params.tabstrip_index = drop_index; | |
| 1647 | |
| 1648 if (drop_before) { | |
| 1649 params.disposition = NEW_FOREGROUND_TAB; | |
| 1650 } else { | |
| 1651 params.disposition = CURRENT_TAB; | |
| 1652 params.source_contents = model_->GetTabContentsAt(drop_index); | |
| 1653 } | |
| 1654 | |
| 1655 browser::Navigate(¶ms); | |
| 1656 | |
| 1657 return true; | |
| 1658 } | |
| 1659 | |
| 1660 // static | |
| 1661 GdkPixbuf* TabStripGtk::GetDropArrowImage(bool is_down) { | |
| 1662 return ResourceBundle::GetSharedInstance().GetPixbufNamed( | |
| 1663 is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP); | |
| 1664 } | |
| 1665 | |
| 1666 // TabStripGtk::DropInfo ------------------------------------------------------- | |
| 1667 | |
| 1668 TabStripGtk::DropInfo::DropInfo(int drop_index, bool drop_before, | |
| 1669 bool point_down) | |
| 1670 : drop_index(drop_index), | |
| 1671 drop_before(drop_before), | |
| 1672 point_down(point_down) { | |
| 1673 CreateContainer(); | |
| 1674 drop_arrow = GetDropArrowImage(point_down); | |
| 1675 } | |
| 1676 | |
| 1677 TabStripGtk::DropInfo::~DropInfo() { | |
| 1678 DestroyContainer(); | |
| 1679 } | |
| 1680 | |
| 1681 gboolean TabStripGtk::DropInfo::OnExposeEvent(GtkWidget* widget, | |
| 1682 GdkEventExpose* event) { | |
| 1683 if (gtk_util::IsScreenComposited()) { | |
| 1684 SetContainerTransparency(); | |
| 1685 } else { | |
| 1686 SetContainerShapeMask(); | |
| 1687 } | |
| 1688 | |
| 1689 gdk_pixbuf_render_to_drawable(drop_arrow, | |
| 1690 container->window, | |
| 1691 0, 0, 0, | |
| 1692 0, 0, | |
| 1693 drop_indicator_width, | |
| 1694 drop_indicator_height, | |
| 1695 GDK_RGB_DITHER_NONE, 0, 0); | |
| 1696 | |
| 1697 return FALSE; | |
| 1698 } | |
| 1699 | |
| 1700 // Sets the color map of the container window to allow the window to be | |
| 1701 // transparent. | |
| 1702 void TabStripGtk::DropInfo::SetContainerColorMap() { | |
| 1703 GdkScreen* screen = gtk_widget_get_screen(container); | |
| 1704 GdkColormap* colormap = gdk_screen_get_rgba_colormap(screen); | |
| 1705 | |
| 1706 // If rgba is not available, use rgb instead. | |
| 1707 if (!colormap) | |
| 1708 colormap = gdk_screen_get_rgb_colormap(screen); | |
| 1709 | |
| 1710 gtk_widget_set_colormap(container, colormap); | |
| 1711 } | |
| 1712 | |
| 1713 // Sets full transparency for the container window. This is used if | |
| 1714 // compositing is available for the screen. | |
| 1715 void TabStripGtk::DropInfo::SetContainerTransparency() { | |
| 1716 cairo_t* cairo_context = gdk_cairo_create(container->window); | |
| 1717 if (!cairo_context) | |
| 1718 return; | |
| 1719 | |
| 1720 // Make the background of the dragged tab window fully transparent. All of | |
| 1721 // the content of the window (child widgets) will be completely opaque. | |
| 1722 | |
| 1723 cairo_scale(cairo_context, static_cast<double>(drop_indicator_width), | |
| 1724 static_cast<double>(drop_indicator_height)); | |
| 1725 cairo_set_source_rgba(cairo_context, 1.0f, 1.0f, 1.0f, 0.0f); | |
| 1726 cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); | |
| 1727 cairo_paint(cairo_context); | |
| 1728 cairo_destroy(cairo_context); | |
| 1729 } | |
| 1730 | |
| 1731 // Sets the shape mask for the container window to emulate a transparent | |
| 1732 // container window. This is used if compositing is not available for the | |
| 1733 // screen. | |
| 1734 void TabStripGtk::DropInfo::SetContainerShapeMask() { | |
| 1735 // Create a 1bpp bitmap the size of |container|. | |
| 1736 GdkPixmap* pixmap = gdk_pixmap_new(NULL, | |
| 1737 drop_indicator_width, | |
| 1738 drop_indicator_height, 1); | |
| 1739 cairo_t* cairo_context = gdk_cairo_create(GDK_DRAWABLE(pixmap)); | |
| 1740 | |
| 1741 // Set the transparency. | |
| 1742 cairo_set_source_rgba(cairo_context, 1, 1, 1, 0); | |
| 1743 | |
| 1744 // Blit the rendered bitmap into a pixmap. Any pixel set in the pixmap will | |
| 1745 // be opaque in the container window. | |
| 1746 cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); | |
| 1747 gdk_cairo_set_source_pixbuf(cairo_context, drop_arrow, 0, 0); | |
| 1748 cairo_paint(cairo_context); | |
| 1749 cairo_destroy(cairo_context); | |
| 1750 | |
| 1751 // Set the shape mask. | |
| 1752 gdk_window_shape_combine_mask(container->window, pixmap, 0, 0); | |
| 1753 g_object_unref(pixmap); | |
| 1754 } | |
| 1755 | |
| 1756 void TabStripGtk::DropInfo::CreateContainer() { | |
| 1757 container = gtk_window_new(GTK_WINDOW_POPUP); | |
| 1758 SetContainerColorMap(); | |
| 1759 gtk_widget_set_app_paintable(container, TRUE); | |
| 1760 g_signal_connect(container, "expose-event", | |
| 1761 G_CALLBACK(OnExposeEventThunk), this); | |
| 1762 gtk_widget_add_events(container, GDK_STRUCTURE_MASK); | |
| 1763 gtk_window_move(GTK_WINDOW(container), 0, 0); | |
| 1764 gtk_window_resize(GTK_WINDOW(container), | |
| 1765 drop_indicator_width, drop_indicator_height); | |
| 1766 gtk_widget_show_all(container); | |
| 1767 } | |
| 1768 | |
| 1769 void TabStripGtk::DropInfo::DestroyContainer() { | |
| 1770 if (GTK_IS_WIDGET(container)) | |
| 1771 gtk_widget_destroy(container); | |
| 1772 } | |
| 1773 | |
| 1774 void TabStripGtk::StopAnimation() { | |
| 1775 if (active_animation_.get()) | |
| 1776 active_animation_->Stop(); | |
| 1777 } | |
| 1778 | |
| 1779 // Called from: | |
| 1780 // - animation tick | |
| 1781 void TabStripGtk::AnimationLayout(double unselected_width) { | |
| 1782 int tab_height = TabGtk::GetStandardSize().height(); | |
| 1783 double tab_x = tab_start_x(); | |
| 1784 for (int i = 0; i < GetTabCount(); ++i) { | |
| 1785 TabAnimation* animation = active_animation_.get(); | |
| 1786 if (animation) | |
| 1787 tab_x += animation->GetGapWidth(i); | |
| 1788 double tab_width = TabAnimation::GetCurrentTabWidth(this, animation, i); | |
| 1789 double end_of_tab = tab_x + tab_width; | |
| 1790 int rounded_tab_x = Round(tab_x); | |
| 1791 TabGtk* tab = GetTabAt(i); | |
| 1792 gfx::Rect bounds(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, | |
| 1793 tab_height); | |
| 1794 SetTabBounds(tab, bounds); | |
| 1795 tab_x = end_of_tab + GetTabHOffset(i + 1); | |
| 1796 } | |
| 1797 LayoutNewTabButton(tab_x, unselected_width); | |
| 1798 } | |
| 1799 | |
| 1800 void TabStripGtk::StartInsertTabAnimation(int index) { | |
| 1801 // The TabStrip can now use its entire width to lay out Tabs. | |
| 1802 available_width_for_tabs_ = -1; | |
| 1803 StopAnimation(); | |
| 1804 active_animation_.reset(new InsertTabAnimation(this, index)); | |
| 1805 active_animation_->Start(); | |
| 1806 } | |
| 1807 | |
| 1808 void TabStripGtk::StartRemoveTabAnimation(int index, TabContents* contents) { | |
| 1809 if (active_animation_.get()) { | |
| 1810 // Some animations (e.g. MoveTabAnimation) cause there to be a Layout when | |
| 1811 // they're completed (which includes canceled). Since |tab_data_| is now | |
| 1812 // inconsistent with TabStripModel, doing this Layout will crash now, so | |
| 1813 // we ask the MoveTabAnimation to skip its Layout (the state will be | |
| 1814 // corrected by the RemoveTabAnimation we're about to initiate). | |
| 1815 active_animation_->set_layout_on_completion(false); | |
| 1816 active_animation_->Stop(); | |
| 1817 } | |
| 1818 | |
| 1819 active_animation_.reset(new RemoveTabAnimation(this, index, contents)); | |
| 1820 active_animation_->Start(); | |
| 1821 } | |
| 1822 | |
| 1823 void TabStripGtk::StartMoveTabAnimation(int from_index, int to_index) { | |
| 1824 StopAnimation(); | |
| 1825 active_animation_.reset(new MoveTabAnimation(this, from_index, to_index)); | |
| 1826 active_animation_->Start(); | |
| 1827 } | |
| 1828 | |
| 1829 void TabStripGtk::StartResizeLayoutAnimation() { | |
| 1830 StopAnimation(); | |
| 1831 active_animation_.reset(new ResizeLayoutAnimation(this)); | |
| 1832 active_animation_->Start(); | |
| 1833 } | |
| 1834 | |
| 1835 void TabStripGtk::StartMiniTabAnimation(int index) { | |
| 1836 StopAnimation(); | |
| 1837 active_animation_.reset(new MiniTabAnimation(this, index)); | |
| 1838 active_animation_->Start(); | |
| 1839 } | |
| 1840 | |
| 1841 void TabStripGtk::StartMiniMoveTabAnimation(int from_index, | |
| 1842 int to_index, | |
| 1843 const gfx::Rect& start_bounds) { | |
| 1844 StopAnimation(); | |
| 1845 active_animation_.reset( | |
| 1846 new MiniMoveAnimation(this, from_index, to_index, start_bounds)); | |
| 1847 active_animation_->Start(); | |
| 1848 } | |
| 1849 | |
| 1850 void TabStripGtk::FinishAnimation(TabStripGtk::TabAnimation* animation, | |
| 1851 bool layout) { | |
| 1852 active_animation_.reset(NULL); | |
| 1853 | |
| 1854 // Reset the animation state of each tab. | |
| 1855 for (int i = 0, count = GetTabCount(); i < count; ++i) | |
| 1856 GetTabAt(i)->set_animating_mini_change(false); | |
| 1857 | |
| 1858 if (layout) | |
| 1859 Layout(); | |
| 1860 } | |
| 1861 | |
| 1862 gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event) { | |
| 1863 if (gdk_region_empty(event->region)) | |
| 1864 return TRUE; | |
| 1865 | |
| 1866 // If we're only repainting favicons, optimize the paint path and only draw | |
| 1867 // the favicons. | |
| 1868 GdkRectangle* rects; | |
| 1869 gint num_rects; | |
| 1870 gdk_region_get_rectangles(event->region, &rects, &num_rects); | |
| 1871 qsort(rects, num_rects, sizeof(GdkRectangle), CompareGdkRectangles); | |
| 1872 std::vector<int> tabs_to_repaint; | |
| 1873 if (!IsDragSessionActive() && | |
| 1874 CanPaintOnlyFavIcons(rects, num_rects, &tabs_to_repaint)) { | |
| 1875 PaintOnlyFavIcons(event, tabs_to_repaint); | |
| 1876 g_free(rects); | |
| 1877 return TRUE; | |
| 1878 } | |
| 1879 g_free(rects); | |
| 1880 | |
| 1881 // TODO(jhawkins): Ideally we'd like to only draw what's needed in the damage | |
| 1882 // rect, but the tab widgets overlap each other, and painting on one widget | |
| 1883 // will cause an expose-event to be sent to the widgets underneath. The | |
| 1884 // underlying widget does not need to be redrawn as we control the order of | |
| 1885 // expose-events. Currently we hack it to redraw the entire tabstrip. We | |
| 1886 // could change the damage rect to just contain the tabs + the new tab button. | |
| 1887 event->area.x = 0; | |
| 1888 event->area.y = 0; | |
| 1889 event->area.width = bounds_.width(); | |
| 1890 event->area.height = bounds_.height(); | |
| 1891 gdk_region_union_with_rect(event->region, &event->area); | |
| 1892 | |
| 1893 // Paint the New Tab button. | |
| 1894 gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()), | |
| 1895 newtab_button_->widget(), event); | |
| 1896 | |
| 1897 // Paint the tabs in reverse order, so they stack to the left. | |
| 1898 TabGtk* selected_tab = NULL; | |
| 1899 int tab_count = GetTabCount(); | |
| 1900 for (int i = tab_count - 1; i >= 0; --i) { | |
| 1901 TabGtk* tab = GetTabAt(i); | |
| 1902 // We must ask the _Tab's_ model, not ourselves, because in some situations | |
| 1903 // the model will be different to this object, e.g. when a Tab is being | |
| 1904 // removed after its TabContents has been destroyed. | |
| 1905 if (!tab->IsSelected()) { | |
| 1906 gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()), | |
| 1907 tab->widget(), event); | |
| 1908 } else { | |
| 1909 selected_tab = tab; | |
| 1910 } | |
| 1911 } | |
| 1912 | |
| 1913 // Paint the selected tab last, so it overlaps all the others. | |
| 1914 if (selected_tab) { | |
| 1915 gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()), | |
| 1916 selected_tab->widget(), event); | |
| 1917 } | |
| 1918 | |
| 1919 return TRUE; | |
| 1920 } | |
| 1921 | |
| 1922 void TabStripGtk::OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) { | |
| 1923 gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y, | |
| 1924 allocation->width, allocation->height); | |
| 1925 | |
| 1926 // Nothing to do if the bounds are the same. If we don't catch this, we'll | |
| 1927 // get an infinite loop of size-allocate signals. | |
| 1928 if (bounds_ == bounds) | |
| 1929 return; | |
| 1930 | |
| 1931 SetBounds(bounds); | |
| 1932 | |
| 1933 // No tabs, nothing to layout. This happens when a browser window is created | |
| 1934 // and shown before tabs are added (as in a popup window). | |
| 1935 if (GetTabCount() == 0) | |
| 1936 return; | |
| 1937 | |
| 1938 // When there is only one tab, Layout() so we don't animate it. With more | |
| 1939 // tabs, do ResizeLayoutTabs(). In RTL(), we will also need to manually | |
| 1940 // Layout() when ResizeLayoutTabs() is a no-op. | |
| 1941 if ((GetTabCount() == 1) || (!ResizeLayoutTabs() && base::i18n::IsRTL())) | |
| 1942 Layout(); | |
| 1943 } | |
| 1944 | |
| 1945 gboolean TabStripGtk::OnDragMotion(GtkWidget* widget, GdkDragContext* context, | |
| 1946 gint x, gint y, guint time) { | |
| 1947 UpdateDropIndex(context, x, y); | |
| 1948 return TRUE; | |
| 1949 } | |
| 1950 | |
| 1951 gboolean TabStripGtk::OnDragDrop(GtkWidget* widget, GdkDragContext* context, | |
| 1952 gint x, gint y, guint time) { | |
| 1953 if (!drop_info_.get()) | |
| 1954 return FALSE; | |
| 1955 | |
| 1956 GdkAtom target = gtk_drag_dest_find_target(widget, context, NULL); | |
| 1957 if (target != GDK_NONE) | |
| 1958 gtk_drag_finish(context, FALSE, FALSE, time); | |
| 1959 else | |
| 1960 gtk_drag_get_data(widget, context, target, time); | |
| 1961 | |
| 1962 return TRUE; | |
| 1963 } | |
| 1964 | |
| 1965 gboolean TabStripGtk::OnDragLeave(GtkWidget* widget, GdkDragContext* context, | |
| 1966 guint time) { | |
| 1967 // Destroy the drop indicator. | |
| 1968 drop_info_->DestroyContainer(); | |
| 1969 return FALSE; | |
| 1970 } | |
| 1971 | |
| 1972 gboolean TabStripGtk::OnDragDataReceived(GtkWidget* widget, | |
| 1973 GdkDragContext* context, | |
| 1974 gint x, gint y, | |
| 1975 GtkSelectionData* data, | |
| 1976 guint info, guint time) { | |
| 1977 bool success = false; | |
| 1978 | |
| 1979 if (info == gtk_dnd_util::TEXT_URI_LIST || | |
| 1980 info == gtk_dnd_util::NETSCAPE_URL || | |
| 1981 info == gtk_dnd_util::TEXT_PLAIN) { | |
| 1982 success = CompleteDrop(data->data, info == gtk_dnd_util::TEXT_PLAIN); | |
| 1983 } | |
| 1984 | |
| 1985 gtk_drag_finish(context, success, success, time); | |
| 1986 return TRUE; | |
| 1987 } | |
| 1988 | |
| 1989 void TabStripGtk::OnNewTabClicked(GtkWidget* widget) { | |
| 1990 GdkEvent* event = gtk_get_current_event(); | |
| 1991 DCHECK_EQ(event->type, GDK_BUTTON_RELEASE); | |
| 1992 int mouse_button = event->button.button; | |
| 1993 gdk_event_free(event); | |
| 1994 | |
| 1995 switch (mouse_button) { | |
| 1996 case 1: | |
| 1997 model_->delegate()->AddBlankTab(true); | |
| 1998 break; | |
| 1999 case 2: { | |
| 2000 // On middle-click, try to parse the PRIMARY selection as a URL and load | |
| 2001 // it instead of creating a blank page. | |
| 2002 GURL url; | |
| 2003 if (!gtk_util::URLFromPrimarySelection(model_->profile(), &url)) | |
| 2004 return; | |
| 2005 | |
| 2006 Browser* browser = window_->browser(); | |
| 2007 DCHECK(browser); | |
| 2008 browser->AddSelectedTabWithURL(url, PageTransition::TYPED); | |
| 2009 break; | |
| 2010 } | |
| 2011 default: | |
| 2012 NOTREACHED() << "Got click on new tab button with unhandled mouse " | |
| 2013 << "button " << mouse_button; | |
| 2014 } | |
| 2015 } | |
| 2016 | |
| 2017 void TabStripGtk::SetTabBounds(TabGtk* tab, const gfx::Rect& bounds) { | |
| 2018 gfx::Rect bds = bounds; | |
| 2019 bds.set_x(gtk_util::MirroredLeftPointForRect(tabstrip_.get(), bounds)); | |
| 2020 tab->SetBounds(bds); | |
| 2021 gtk_fixed_move(GTK_FIXED(tabstrip_.get()), tab->widget(), | |
| 2022 bds.x(), bds.y()); | |
| 2023 } | |
| 2024 | |
| 2025 bool TabStripGtk::CanPaintOnlyFavIcons(const GdkRectangle* rects, | |
| 2026 int num_rects, std::vector<int>* tabs_to_paint) { | |
| 2027 // |rects| are sorted so we just need to scan from left to right and compare | |
| 2028 // it to the tab favicon positions from left to right. | |
| 2029 int t = 0; | |
| 2030 for (int r = 0; r < num_rects; ++r) { | |
| 2031 while (t < GetTabCount()) { | |
| 2032 TabGtk* tab = GetTabAt(t); | |
| 2033 if (GdkRectMatchesTabFavIconBounds(rects[r], tab) && | |
| 2034 tab->ShouldShowIcon()) { | |
| 2035 tabs_to_paint->push_back(t); | |
| 2036 ++t; | |
| 2037 break; | |
| 2038 } | |
| 2039 ++t; | |
| 2040 } | |
| 2041 } | |
| 2042 return static_cast<int>(tabs_to_paint->size()) == num_rects; | |
| 2043 } | |
| 2044 | |
| 2045 void TabStripGtk::PaintOnlyFavIcons(GdkEventExpose* event, | |
| 2046 const std::vector<int>& tabs_to_paint) { | |
| 2047 for (size_t i = 0; i < tabs_to_paint.size(); ++i) | |
| 2048 GetTabAt(tabs_to_paint[i])->PaintFavIconArea(event); | |
| 2049 } | |
| 2050 | |
| 2051 CustomDrawButton* TabStripGtk::MakeNewTabButton() { | |
| 2052 CustomDrawButton* button = new CustomDrawButton(IDR_NEWTAB_BUTTON, | |
| 2053 IDR_NEWTAB_BUTTON_P, IDR_NEWTAB_BUTTON_H, 0); | |
| 2054 | |
| 2055 // Let the middle mouse button initiate clicks as well. | |
| 2056 gtk_util::SetButtonTriggersNavigation(button->widget()); | |
| 2057 g_signal_connect(button->widget(), "clicked", | |
| 2058 G_CALLBACK(OnNewTabClickedThunk), this); | |
| 2059 GTK_WIDGET_UNSET_FLAGS(button->widget(), GTK_CAN_FOCUS); | |
| 2060 gtk_fixed_put(GTK_FIXED(tabstrip_.get()), button->widget(), 0, 0); | |
| 2061 | |
| 2062 return button; | |
| 2063 } | |
| OLD | NEW |