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