OLD | NEW |
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/views/tabs/tab_strip.h" | 5 #include "chrome/browser/views/tabs/tab_strip.h" |
6 | 6 |
7 #include "base/gfx/size.h" | 7 #include "base/gfx/size.h" |
8 #include "chrome/app/theme/theme_resources.h" | 8 #include "chrome/app/theme/theme_resources.h" |
9 #include "chrome/browser/profile.h" | 9 #include "chrome/browser/profile.h" |
10 #include "chrome/browser/tab_contents.h" | 10 #include "chrome/browser/tab_contents.h" |
(...skipping 424 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
435 : TabAnimation(tabstrip, RESIZE) { | 435 : TabAnimation(tabstrip, RESIZE) { |
436 int tab_count = tabstrip->GetTabCount(); | 436 int tab_count = tabstrip->GetTabCount(); |
437 GenerateStartAndEndWidths(tab_count, tab_count); | 437 GenerateStartAndEndWidths(tab_count, tab_count); |
438 InitStartState(); | 438 InitStartState(); |
439 } | 439 } |
440 virtual ~ResizeLayoutAnimation() { | 440 virtual ~ResizeLayoutAnimation() { |
441 } | 441 } |
442 | 442 |
443 // Overridden from AnimationDelegate: | 443 // Overridden from AnimationDelegate: |
444 virtual void AnimationEnded(const Animation* animation) { | 444 virtual void AnimationEnded(const Animation* animation) { |
445 tabstrip_->resize_layout_scheduled_ = false; | 445 tabstrip_->needs_resize_layout_ = false; |
446 TabStrip::TabAnimation::AnimationEnded(animation); | 446 TabStrip::TabAnimation::AnimationEnded(animation); |
447 } | 447 } |
448 | 448 |
449 protected: | 449 protected: |
450 // Overridden from TabStrip::TabAnimation: | 450 // Overridden from TabStrip::TabAnimation: |
451 virtual int GetDuration() const { | 451 virtual int GetDuration() const { |
452 return kResizeLayoutAnimationDurationMs; | 452 return kResizeLayoutAnimationDurationMs; |
453 } | 453 } |
454 | 454 |
455 virtual double GetWidthForTab(int index) const { | 455 virtual double GetWidthForTab(int index) const { |
(...skipping 24 matching lines...) Expand all Loading... |
480 DISALLOW_EVIL_CONSTRUCTORS(ResizeLayoutAnimation); | 480 DISALLOW_EVIL_CONSTRUCTORS(ResizeLayoutAnimation); |
481 }; | 481 }; |
482 | 482 |
483 /////////////////////////////////////////////////////////////////////////////// | 483 /////////////////////////////////////////////////////////////////////////////// |
484 // TabStrip, public: | 484 // TabStrip, public: |
485 | 485 |
486 TabStrip::TabStrip(TabStripModel* model) | 486 TabStrip::TabStrip(TabStripModel* model) |
487 : model_(model), | 487 : model_(model), |
488 resize_layout_factory_(this), | 488 resize_layout_factory_(this), |
489 added_as_message_loop_observer_(false), | 489 added_as_message_loop_observer_(false), |
490 resize_layout_scheduled_(false), | 490 needs_resize_layout_(false), |
491 current_unselected_width_(Tab::GetStandardSize().width()), | 491 current_unselected_width_(Tab::GetStandardSize().width()), |
492 current_selected_width_(Tab::GetStandardSize().width()), | 492 current_selected_width_(Tab::GetStandardSize().width()), |
493 available_width_for_tabs_(-1) { | 493 available_width_for_tabs_(-1) { |
494 Init(); | 494 Init(); |
495 } | 495 } |
496 | 496 |
497 TabStrip::~TabStrip() { | 497 TabStrip::~TabStrip() { |
498 // TODO(beng): (1031854) Restore this line once XPFrame/VistaFrame are dead. | 498 // TODO(beng): (1031854) Restore this line once XPFrame/VistaFrame are dead. |
499 //model_->RemoveObserver(this); | 499 //model_->RemoveObserver(this); |
500 | 500 |
(...skipping 361 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
862 | 862 |
863 void TabStrip::TabSelectedAt(TabContents* old_contents, | 863 void TabStrip::TabSelectedAt(TabContents* old_contents, |
864 TabContents* new_contents, | 864 TabContents* new_contents, |
865 int index, | 865 int index, |
866 bool user_gesture) { | 866 bool user_gesture) { |
867 DCHECK(index >= 0 && index < GetTabCount()); | 867 DCHECK(index >= 0 && index < GetTabCount()); |
868 if (CanUpdateDisplay()) { | 868 if (CanUpdateDisplay()) { |
869 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are | 869 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are |
870 // a different size to the selected ones. | 870 // a different size to the selected ones. |
871 bool tiny_tabs = current_unselected_width_ != current_selected_width_; | 871 bool tiny_tabs = current_unselected_width_ != current_selected_width_; |
872 if (!IsAnimating() && (!resize_layout_scheduled_ || tiny_tabs)) { | 872 if (!IsAnimating() && (!needs_resize_layout_ || tiny_tabs)) { |
873 Layout(); | 873 Layout(); |
874 } else { | 874 } else { |
875 SchedulePaint(); | 875 SchedulePaint(); |
876 } | 876 } |
877 } | 877 } |
878 } | 878 } |
879 | 879 |
880 void TabStrip::TabMoved(TabContents* contents, int from_index, int to_index) { | 880 void TabStrip::TabMoved(TabContents* contents, int from_index, int to_index) { |
881 Tab* tab = GetTabAt(from_index); | 881 Tab* tab = GetTabAt(from_index); |
882 Tab* other_tab = GetTabAt(to_index); | 882 Tab* other_tab = GetTabAt(to_index); |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
940 int tab_index = GetIndexOfTab(tab); | 940 int tab_index = GetIndexOfTab(tab); |
941 if (model_->ContainsIndex(tab_index)) { | 941 if (model_->ContainsIndex(tab_index)) { |
942 TabContents* contents = model_->GetTabContentsAt(tab_index); | 942 TabContents* contents = model_->GetTabContentsAt(tab_index); |
943 if (contents) | 943 if (contents) |
944 UserMetrics::RecordAction(L"CloseTab_Mouse", contents->profile()); | 944 UserMetrics::RecordAction(L"CloseTab_Mouse", contents->profile()); |
945 Tab* last_tab = GetTabAt(GetTabCount() - 1); | 945 Tab* last_tab = GetTabAt(GetTabCount() - 1); |
946 // Limit the width available to the TabStrip for laying out Tabs, so that | 946 // Limit the width available to the TabStrip for laying out Tabs, so that |
947 // Tabs are not resized until a later time (when the mouse pointer leaves | 947 // Tabs are not resized until a later time (when the mouse pointer leaves |
948 // the TabStrip). | 948 // the TabStrip). |
949 available_width_for_tabs_ = GetAvailableWidthForTabs(last_tab); | 949 available_width_for_tabs_ = GetAvailableWidthForTabs(last_tab); |
950 resize_layout_scheduled_ = true; | 950 needs_resize_layout_ = true; |
951 AddMessageLoopObserver(); | |
952 model_->CloseTabContentsAt(tab_index); | 951 model_->CloseTabContentsAt(tab_index); |
953 } | 952 } |
954 } | 953 } |
955 | 954 |
956 bool TabStrip::IsCommandEnabledForTab( | 955 bool TabStrip::IsCommandEnabledForTab( |
957 TabStripModel::ContextMenuCommand command_id, const Tab* tab) const { | 956 TabStripModel::ContextMenuCommand command_id, const Tab* tab) const { |
958 int index = GetIndexOfTab(tab); | 957 int index = GetIndexOfTab(tab); |
959 if (model_->ContainsIndex(index)) | 958 if (model_->ContainsIndex(index)) |
960 return model_->IsContextMenuCommandEnabled(index, command_id); | 959 return model_->IsContextMenuCommandEnabled(index, command_id); |
961 return false; | 960 return false; |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1046 model_->AddBlankTab(true); | 1045 model_->AddBlankTab(true); |
1047 } | 1046 } |
1048 | 1047 |
1049 /////////////////////////////////////////////////////////////////////////////// | 1048 /////////////////////////////////////////////////////////////////////////////// |
1050 // TabStrip, MessageLoop::Observer implementation: | 1049 // TabStrip, MessageLoop::Observer implementation: |
1051 | 1050 |
1052 void TabStrip::WillProcessMessage(const MSG& msg) { | 1051 void TabStrip::WillProcessMessage(const MSG& msg) { |
1053 } | 1052 } |
1054 | 1053 |
1055 void TabStrip::DidProcessMessage(const MSG& msg) { | 1054 void TabStrip::DidProcessMessage(const MSG& msg) { |
1056 // We spy on three different Windows messages here to see if the mouse has | |
1057 // moved out of the bounds of the tabstrip, which we use as our cue to kick | |
1058 // of the resize animation. The messages are: | |
1059 // | |
1060 // WM_MOUSEMOVE: | |
1061 // For when the mouse moves from the tabstrip over into the rest of the | |
1062 // browser UI, i.e. within the bounds of the same windows HWND. | |
1063 // WM_MOUSELEAVE: | |
1064 // For when the mouse moves very rapidly from a tab closed in the middle of | |
1065 // the tabstrip (_not_ the end) out of the bounds of the browser's HWND and | |
1066 // over some other HWND. | |
1067 // WM_NCMOUSELEAVE: | |
1068 // For when the mouse moves very rapidly from the end of the tabstrip (when | |
1069 // the last tab is closed and the mouse is left floating over the title | |
1070 // bar). Because the empty area of the tabstrip at the end of the title bar | |
1071 // is registered by the ChromeFrame as part of the "caption" area of the | |
1072 // window (the frame's OnNCHitTest method returns HTCAPTION for this | |
1073 // region), the frame's HWND receives a WM_MOUSEMOVE message immediately, | |
1074 // because as far as it is concerned the mouse has _left_ the client area | |
1075 // of the window (and is now over the non-client area). To be notified | |
1076 // again when the mouse leaves the _non-client_ area, we use the | |
1077 // WM_NCMOUSELEAVE message, which causes us to re-evaluate the cursor | |
1078 // position and correctly resize the tabstrip. | |
1079 // | |
1080 switch (msg.message) { | 1055 switch (msg.message) { |
| 1056 // We spy on three different Windows messages here to see if the mouse has |
| 1057 // moved out of the bounds of the tabstrip, which we use as our cue to kick |
| 1058 // of the resize animation. The messages are: |
| 1059 // |
| 1060 // WM_MOUSEMOVE: |
| 1061 // For when the mouse moves from the tabstrip over into the rest of the |
| 1062 // browser UI, i.e. within the bounds of the same windows HWND. |
| 1063 // WM_MOUSELEAVE: |
| 1064 // For when the mouse moves very rapidly from a tab closed in the middle |
| 1065 // of the tabstrip (_not_ the end) out of the bounds of the browser's |
| 1066 // HWND and over some other HWND. |
| 1067 // WM_NCMOUSELEAVE: |
| 1068 // For when the mouse moves very rapidly from the end of the tabstrip |
| 1069 // (when the last tab is closed and the mouse is left floating over the |
| 1070 // title bar). Because the empty area of the tabstrip at the end of the |
| 1071 // title bar is registered by the ChromeFrame as part of the "caption" |
| 1072 // area of the window (the frame's OnNCHitTest method returns HTCAPTION |
| 1073 // for this region), the frame's HWND receives a WM_MOUSEMOVE message |
| 1074 // immediately, because as far as it is concerned the mouse has _left_ |
| 1075 // the client area of the window (and is now over the non-client area). |
| 1076 // To be notified again when the mouse leaves the _non-client_ area, we |
| 1077 // use the WM_NCMOUSELEAVE message, which causes us to re-evaluate the |
| 1078 // cursor position and correctly resize the tabstrip. |
| 1079 // |
1081 case WM_MOUSEMOVE: | 1080 case WM_MOUSEMOVE: |
1082 case WM_MOUSELEAVE: | 1081 case WM_MOUSELEAVE: |
1083 case WM_NCMOUSELEAVE: | 1082 case WM_NCMOUSELEAVE: |
| 1083 if (!needs_resize_layout_) |
| 1084 break; |
1084 if (!IsCursorInTabStripZone()) { | 1085 if (!IsCursorInTabStripZone()) { |
1085 // Mouse moved outside the tab slop zone, start a timer to do a resize | 1086 // Mouse moved outside the tab slop zone, start a timer to do a resize |
1086 // layout after a short while... | 1087 // layout after a short while... |
1087 if (resize_layout_factory_.empty()) { | 1088 if (resize_layout_factory_.empty()) { |
1088 MessageLoop::current()->PostDelayedTask(FROM_HERE, | 1089 MessageLoop::current()->PostDelayedTask(FROM_HERE, |
1089 resize_layout_factory_.NewRunnableMethod( | 1090 resize_layout_factory_.NewRunnableMethod( |
1090 &TabStrip::ResizeLayoutTabs), | 1091 &TabStrip::ResizeLayoutTabs), |
1091 kResizeTabsTimeMs); | 1092 kResizeTabsTimeMs); |
1092 } | 1093 } |
1093 } else { | 1094 } else { |
1094 // Mouse moved quickly out of the tab strip and then into it again, so | 1095 // Mouse moved quickly out of the tab strip and then into it again, so |
1095 // cancel the timer so that the strip doesn't move when the mouse moves | 1096 // cancel the timer so that the strip doesn't move when the mouse moves |
1096 // back over it. | 1097 // back over it. |
1097 resize_layout_factory_.RevokeAll(); | 1098 resize_layout_factory_.RevokeAll(); |
1098 } | 1099 } |
1099 break; | 1100 break; |
| 1101 // We also watch out for mouse-wheel messages here to allow the user to |
| 1102 // leaf through the tabs by scrolling while the mouse cursor is in the tab |
| 1103 // strip zone. |
| 1104 case WM_MOUSEWHEEL: |
| 1105 if (!IsCursorInTabStripZone()) |
| 1106 break; |
| 1107 CHECK(model_); |
| 1108 // A positive value indicates that the wheel was rotated away from the |
| 1109 // user; a negative value indicates rotation toward the user. |
| 1110 if (GET_WHEEL_DELTA_WPARAM(msg.wParam) > 0) |
| 1111 model_->SelectPreviousTab(); |
| 1112 else |
| 1113 model_->SelectNextTab(); |
| 1114 break; |
1100 } | 1115 } |
1101 } | 1116 } |
1102 | 1117 |
1103 /////////////////////////////////////////////////////////////////////////////// | 1118 /////////////////////////////////////////////////////////////////////////////// |
1104 // TabStrip, private: | 1119 // TabStrip, private: |
1105 | 1120 |
1106 void TabStrip::Init() { | 1121 void TabStrip::Init() { |
1107 model_->AddObserver(this); | 1122 model_->AddObserver(this); |
1108 newtab_button_ = new NewTabButton; | 1123 newtab_button_ = new NewTabButton; |
1109 newtab_button_->SetListener(this, TabStripModel::kNoTab); | 1124 newtab_button_->SetListener(this, TabStripModel::kNoTab); |
(...skipping 12 matching lines...) Expand all Loading... |
1122 | 1137 |
1123 newtab_button_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_NEWTAB)); | 1138 newtab_button_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_NEWTAB)); |
1124 AddChildView(newtab_button_); | 1139 AddChildView(newtab_button_); |
1125 | 1140 |
1126 if (drop_indicator_width == 0) { | 1141 if (drop_indicator_width == 0) { |
1127 // Direction doesn't matter, both images are the same size. | 1142 // Direction doesn't matter, both images are the same size. |
1128 SkBitmap* drop_image = GetDropArrowImage(true); | 1143 SkBitmap* drop_image = GetDropArrowImage(true); |
1129 drop_indicator_width = drop_image->width(); | 1144 drop_indicator_width = drop_image->width(); |
1130 drop_indicator_height = drop_image->height(); | 1145 drop_indicator_height = drop_image->height(); |
1131 } | 1146 } |
| 1147 |
| 1148 AddMessageLoopObserver(); |
1132 } | 1149 } |
1133 | 1150 |
1134 Tab* TabStrip::GetTabAt(int index) const { | 1151 Tab* TabStrip::GetTabAt(int index) const { |
1135 DCHECK(index >= 0 && index < GetTabCount()); | 1152 DCHECK(index >= 0 && index < GetTabCount()); |
1136 return tab_data_.at(index).tab; | 1153 return tab_data_.at(index).tab; |
1137 } | 1154 } |
1138 | 1155 |
1139 Tab* TabStrip::GetTabAtAdjustForAnimation(int index) const { | 1156 Tab* TabStrip::GetTabAtAdjustForAnimation(int index) const { |
1140 if (active_animation_.get() && | 1157 if (active_animation_.get() && |
1141 active_animation_->type() == TabAnimation::REMOVE && | 1158 active_animation_->type() == TabAnimation::REMOVE && |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1217 // Selected width = (total width - (unselected width * (num_tabs - 1))) | 1234 // Selected width = (total width - (unselected width * (num_tabs - 1))) |
1218 *selected_width = std::max(available_width - total_offset - | 1235 *selected_width = std::max(available_width - total_offset - |
1219 (min_unselected_width * (tab_count - 1)), min_selected_width); | 1236 (min_unselected_width * (tab_count - 1)), min_selected_width); |
1220 } | 1237 } |
1221 } | 1238 } |
1222 } | 1239 } |
1223 | 1240 |
1224 void TabStrip::ResizeLayoutTabs() { | 1241 void TabStrip::ResizeLayoutTabs() { |
1225 resize_layout_factory_.RevokeAll(); | 1242 resize_layout_factory_.RevokeAll(); |
1226 | 1243 |
1227 // It is critically important that this is unhooked here, otherwise we will | |
1228 // keep spying on messages forever. | |
1229 RemoveMessageLoopObserver(); | |
1230 | |
1231 available_width_for_tabs_ = -1; | 1244 available_width_for_tabs_ = -1; |
1232 double unselected, selected; | 1245 double unselected, selected; |
1233 GetDesiredTabWidths(GetTabCount(), &unselected, &selected); | 1246 GetDesiredTabWidths(GetTabCount(), &unselected, &selected); |
1234 Tab* first_tab = GetTabAt(0); | 1247 Tab* first_tab = GetTabAt(0); |
1235 int w = Round(first_tab->IsSelected() ? selected : selected); | 1248 int w = Round(first_tab->IsSelected() ? selected : selected); |
1236 | 1249 |
1237 // We only want to run the animation if we're not already at the desired | 1250 // We only want to run the animation if we're not already at the desired |
1238 // size. | 1251 // size. |
1239 if (abs(first_tab->width() - w) > 1) | 1252 if (abs(first_tab->width() - w) > 1) |
1240 StartResizeLayoutAnimation(); | 1253 StartResizeLayoutAnimation(); |
(...skipping 213 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1454 gfx::Rect state(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, | 1467 gfx::Rect state(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, |
1455 tab_height); | 1468 tab_height); |
1456 tab_data_.at(i).ideal_bounds = state; | 1469 tab_data_.at(i).ideal_bounds = state; |
1457 tab_x = end_of_tab + kTabHOffset; | 1470 tab_x = end_of_tab + kTabHOffset; |
1458 } | 1471 } |
1459 } | 1472 } |
1460 | 1473 |
1461 void TabStrip::LayoutNewTabButton(double last_tab_right, | 1474 void TabStrip::LayoutNewTabButton(double last_tab_right, |
1462 double unselected_width) { | 1475 double unselected_width) { |
1463 int delta = abs(Round(unselected_width) - Tab::GetStandardSize().width()); | 1476 int delta = abs(Round(unselected_width) - Tab::GetStandardSize().width()); |
1464 if (delta > 1 && !resize_layout_scheduled_) { | 1477 if (delta > 1 && !needs_resize_layout_) { |
1465 // We're shrinking tabs, so we need to anchor the New Tab button to the | 1478 // We're shrinking tabs, so we need to anchor the New Tab button to the |
1466 // right edge of the TabStrip's bounds, rather than the right edge of the | 1479 // right edge of the TabStrip's bounds, rather than the right edge of the |
1467 // right-most Tab, otherwise it'll bounce when animating. | 1480 // right-most Tab, otherwise it'll bounce when animating. |
1468 newtab_button_->SetBounds(width() - newtab_button_size_.width(), | 1481 newtab_button_->SetBounds(width() - newtab_button_size_.width(), |
1469 kNewTabButtonVOffset, | 1482 kNewTabButtonVOffset, |
1470 newtab_button_size_.width(), | 1483 newtab_button_size_.width(), |
1471 newtab_button_size_.height()); | 1484 newtab_button_size_.height()); |
1472 } else { | 1485 } else { |
1473 newtab_button_->SetBounds( | 1486 newtab_button_->SetBounds( |
1474 Round(last_tab_right - kTabHOffset) + kNewTabButtonHOffset, | 1487 Round(last_tab_right - kTabHOffset) + kNewTabButtonHOffset, |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1566 int TabStrip::GetAvailableWidthForTabs(Tab* last_tab) const { | 1579 int TabStrip::GetAvailableWidthForTabs(Tab* last_tab) const { |
1567 return last_tab->x() + last_tab->width(); | 1580 return last_tab->x() + last_tab->width(); |
1568 } | 1581 } |
1569 | 1582 |
1570 bool TabStrip::IsPointInTab(Tab* tab, const CPoint& point_in_tabstrip_coords) { | 1583 bool TabStrip::IsPointInTab(Tab* tab, const CPoint& point_in_tabstrip_coords) { |
1571 CPoint point_in_tab_coords(point_in_tabstrip_coords); | 1584 CPoint point_in_tab_coords(point_in_tabstrip_coords); |
1572 View::ConvertPointToView(this, tab, &point_in_tab_coords); | 1585 View::ConvertPointToView(this, tab, &point_in_tab_coords); |
1573 return tab->HitTest(point_in_tab_coords); | 1586 return tab->HitTest(point_in_tab_coords); |
1574 } | 1587 } |
1575 | 1588 |
OLD | NEW |