OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this |
| 2 // source code is governed by a BSD-style license that can be found in the |
| 3 // LICENSE file. |
| 4 |
| 5 #include "chrome/browser/views/tabs/tab_strip_2.h" |
| 6 |
| 7 #include "app/gfx/canvas.h" |
| 8 #include "app/slide_animation.h" |
| 9 #include "app/win_util.h" |
| 10 #include "base/command_line.h" |
| 11 #include "base/message_loop.h" |
| 12 #include "chrome/common/chrome_switches.h" |
| 13 #include "views/animator.h" |
| 14 #include "views/screen.h" |
| 15 #include "views/widget/widget.h" |
| 16 #include "views/window/non_client_view.h" |
| 17 #include "views/window/window.h" |
| 18 |
| 19 static const int kHorizontalMoveThreshold = 16; // pixels |
| 20 |
| 21 //////////////////////////////////////////////////////////////////////////////// |
| 22 // TabStrip2, public: |
| 23 |
| 24 TabStrip2::TabStrip2(TabStrip2Model* model) |
| 25 : model_(model), |
| 26 last_move_screen_x_(0), |
| 27 detach_factory_(this), |
| 28 drag_start_factory_(this) { |
| 29 } |
| 30 |
| 31 TabStrip2::~TabStrip2() { |
| 32 } |
| 33 |
| 34 // static |
| 35 bool TabStrip2::Enabled() { |
| 36 return CommandLine::ForCurrentProcess()->HasSwitch( |
| 37 switches::kEnableTabtastic2); |
| 38 } |
| 39 |
| 40 void TabStrip2::AddTabAt(int index) { |
| 41 Tab2* tab = new Tab2(this); |
| 42 int insertion_index = GetInternalIndex(index); |
| 43 tabs_.insert(tabs_.begin() + insertion_index, tab); |
| 44 AddChildView(insertion_index, tab); |
| 45 LayoutImpl(LS_TAB_ADD); |
| 46 } |
| 47 |
| 48 void TabStrip2::RemoveTabAt(int index, Tab2Model* removing_model) { |
| 49 Tab2* tab = GetTabAt(GetInternalIndex(index)); |
| 50 |
| 51 DCHECK(!tab->removing()); |
| 52 tab->set_removing(true); |
| 53 |
| 54 DCHECK(removing_model); |
| 55 tab->SetRemovingModel(removing_model); |
| 56 |
| 57 LayoutImpl(LS_TAB_REMOVE); |
| 58 } |
| 59 |
| 60 void TabStrip2::SelectTabAt(int index) { |
| 61 LayoutImpl(LS_TAB_SELECT); |
| 62 SchedulePaint(); |
| 63 } |
| 64 |
| 65 void TabStrip2::MoveTabAt(int index, int to_index) { |
| 66 int from_index = GetInternalIndex(index); |
| 67 Tab2* tab = GetTabAt(from_index); |
| 68 tabs_.erase(tabs_.begin() + from_index); |
| 69 tabs_.insert(tabs_.begin() + GetInternalIndex(to_index), tab); |
| 70 LayoutImpl(LS_TAB_DRAG_REORDER); |
| 71 } |
| 72 |
| 73 int TabStrip2::GetTabCount() const { |
| 74 return tabs_.size(); |
| 75 } |
| 76 |
| 77 Tab2* TabStrip2::GetTabAt(int index) const { |
| 78 return tabs_.at(index); |
| 79 } |
| 80 |
| 81 int TabStrip2::GetTabIndex(Tab2* tab) const { |
| 82 std::vector<Tab2*>::const_iterator it = find(tabs_.begin(), tabs_.end(), tab); |
| 83 if (it != tabs_.end()) |
| 84 return it - tabs_.begin(); |
| 85 return -1; |
| 86 } |
| 87 |
| 88 int TabStrip2::GetInsertionIndexForPoint(const gfx::Point& point) const { |
| 89 int tab_count = GetTabCount(); |
| 90 for (int i = 0; i < tab_count; ++i) { |
| 91 if (GetTabAt(i)->removing()) |
| 92 continue; |
| 93 gfx::Rect tab_bounds = GetTabAt(i)->bounds(); |
| 94 gfx::Rect tab_left_half = tab_bounds; |
| 95 tab_left_half.set_width(tab_left_half.width() / 2); |
| 96 if (point.x() >= tab_left_half.x() && point.x() <= tab_left_half.right()) |
| 97 return i; |
| 98 gfx::Rect tab_right_half = tab_bounds; |
| 99 tab_right_half.set_x(tab_right_half.width() / 2); |
| 100 tab_right_half.set_width(tab_right_half.x()); |
| 101 if (point.x() > tab_right_half.x() && point.x() <= tab_right_half.right()) |
| 102 if (tab_right_half.Contains(point)) |
| 103 return i + 1; |
| 104 } |
| 105 return tab_count; |
| 106 } |
| 107 |
| 108 gfx::Rect TabStrip2::GetDraggedTabScreenBounds(const gfx::Point& screen_point) { |
| 109 gfx::Point tab_screen_origin(screen_point); |
| 110 tab_screen_origin.Offset(mouse_tab_offset_.x(), mouse_tab_offset_.y()); |
| 111 return gfx::Rect(tab_screen_origin, GetTabAt(0)->bounds().size()); |
| 112 } |
| 113 |
| 114 void TabStrip2::SetDraggedTabBounds(int index, const gfx::Rect& tab_bounds) { |
| 115 // This function should only ever be called goats |
| 116 Tab2* dragged_tab = GetTabAt(index); |
| 117 dragged_tab->SetBounds(tab_bounds); |
| 118 SchedulePaint(); |
| 119 } |
| 120 |
| 121 void TabStrip2::SendDraggedTabHome() { |
| 122 LayoutImpl(LS_TAB_DRAG_REORDER); |
| 123 } |
| 124 |
| 125 void TabStrip2::ResumeDraggingTab(int index, const gfx::Rect& tab_bounds) { |
| 126 MessageLoop::current()->PostTask(FROM_HERE, |
| 127 drag_start_factory_.NewRunnableMethod(&TabStrip2::StartDragTabImpl, index, |
| 128 tab_bounds)); |
| 129 } |
| 130 |
| 131 // static |
| 132 bool TabStrip2::IsDragRearrange(TabStrip2* tabstrip, |
| 133 const gfx::Point& screen_point) { |
| 134 gfx::Point origin; |
| 135 View::ConvertPointToScreen(tabstrip, &origin); |
| 136 gfx::Rect tabstrip_bounds_in_screen_coords(origin, tabstrip->bounds().size()); |
| 137 if (tabstrip_bounds_in_screen_coords.Contains(screen_point)) |
| 138 return true; |
| 139 |
| 140 // The tab is only detached if the tab is moved outside the bounds of the |
| 141 // TabStrip to the left or right, or a certain distance above or below the |
| 142 // TabStrip defined by the vertical detach magnetism below. This is to |
| 143 // prevent accidental detaches when rearranging horizontally. |
| 144 static const int kVerticalDetachMagnetism = 45; |
| 145 |
| 146 bool rearrange = true; |
| 147 if (screen_point.x() < tabstrip_bounds_in_screen_coords.right() && |
| 148 screen_point.x() >= tabstrip_bounds_in_screen_coords.x()) { |
| 149 int lower_threshold = |
| 150 tabstrip_bounds_in_screen_coords.bottom() + kVerticalDetachMagnetism; |
| 151 int upper_threshold = |
| 152 tabstrip_bounds_in_screen_coords.y() - kVerticalDetachMagnetism; |
| 153 return screen_point.y() >= upper_threshold && |
| 154 screen_point.y() <= lower_threshold; |
| 155 } |
| 156 return false; |
| 157 } |
| 158 |
| 159 //////////////////////////////////////////////////////////////////////////////// |
| 160 // TabStrip2, Tab2Model implementation: |
| 161 |
| 162 string16 TabStrip2::GetTitle(Tab2* tab) const { |
| 163 return model_->GetTitle(GetTabIndex(tab)); |
| 164 } |
| 165 |
| 166 bool TabStrip2::IsSelected(Tab2* tab) const { |
| 167 return model_->IsSelected(GetTabIndex(tab)); |
| 168 } |
| 169 |
| 170 void TabStrip2::SelectTab(Tab2* tab) { |
| 171 model_->SelectTabAt(GetTabIndex(tab)); |
| 172 } |
| 173 |
| 174 void TabStrip2::CaptureDragInfo(Tab2* tab, |
| 175 const views::MouseEvent& drag_event) { |
| 176 mouse_tab_offset_ = drag_event.location(); |
| 177 } |
| 178 |
| 179 bool TabStrip2::DragTab(Tab2* tab, const views::MouseEvent& drag_event) { |
| 180 if (!model_->CanDragTabs()) |
| 181 return false; |
| 182 |
| 183 int tab_x = tab->x() + drag_event.location().x() - mouse_tab_offset_.x(); |
| 184 if (tab_x < 0) |
| 185 tab_x = 0; |
| 186 if ((tab_x + tab->width()) > bounds().right()) |
| 187 tab_x = bounds().right() - tab_x - tab->width(); |
| 188 tab->SetBounds(tab_x, tab->y(), tab->width(), tab->height()); |
| 189 SchedulePaint(); |
| 190 |
| 191 int tab_index = GetTabIndex(tab); |
| 192 int dest_index = tab_index; |
| 193 |
| 194 Tab2* next_tab = NULL; |
| 195 Tab2* prev_tab = NULL; |
| 196 int next_tab_index = tab_index + 1; |
| 197 if (next_tab_index < GetTabCount()) |
| 198 next_tab = GetTabAt(next_tab_index); |
| 199 int prev_tab_index = tab_index - 1; |
| 200 if (prev_tab_index >= 0) |
| 201 prev_tab = GetTabAt(prev_tab_index); |
| 202 |
| 203 if (next_tab) { |
| 204 int next_tab_middle_x = next_tab->x() + next_tab->bounds().width() / 2; |
| 205 if (!next_tab->IsAnimating() && tab->bounds().right() > next_tab_middle_x) |
| 206 ++dest_index; |
| 207 } |
| 208 if (prev_tab) { |
| 209 int prev_tab_middle_x = prev_tab->x() + prev_tab->bounds().width() / 2; |
| 210 if (!prev_tab->IsAnimating() && tab->bounds().x() < prev_tab_middle_x) |
| 211 --dest_index; |
| 212 } |
| 213 |
| 214 gfx::Point screen_point = views::Screen::GetCursorScreenPoint(); |
| 215 if (IsDragRearrange(this, screen_point)) { |
| 216 if (abs(screen_point.x() - last_move_screen_x_) > |
| 217 kHorizontalMoveThreshold) { |
| 218 if (dest_index != tab_index) { |
| 219 last_move_screen_x_ = screen_point.x(); |
| 220 model_->MoveTabAt(tab_index, dest_index); |
| 221 } |
| 222 } |
| 223 } else { |
| 224 // We're going to detach. We need to release mouse capture so that further |
| 225 // mouse events will be sent to the appropriate window (the detached window) |
| 226 // and so that we don't recursively create nested message loops (dragging |
| 227 // is done by windows in a nested message loop). |
| 228 ReleaseCapture(); |
| 229 MessageLoop::current()->PostTask(FROM_HERE, |
| 230 detach_factory_.NewRunnableMethod(&TabStrip2::DragDetachTabImpl, |
| 231 tab, tab_index)); |
| 232 } |
| 233 return true; |
| 234 } |
| 235 |
| 236 void TabStrip2::DragEnded(Tab2* tab) { |
| 237 LayoutImpl(LS_TAB_DRAG_NORMALIZE); |
| 238 } |
| 239 |
| 240 views::AnimatorDelegate* TabStrip2::AsAnimatorDelegate() { |
| 241 return this; |
| 242 } |
| 243 |
| 244 //////////////////////////////////////////////////////////////////////////////// |
| 245 // TabStrip2, views::View overrides: |
| 246 |
| 247 gfx::Size TabStrip2::GetPreferredSize() { |
| 248 return gfx::Size(0, 27); |
| 249 } |
| 250 |
| 251 void TabStrip2::Layout() { |
| 252 LayoutImpl(LS_OTHER); |
| 253 } |
| 254 |
| 255 void TabStrip2::Paint(gfx::Canvas* canvas) { |
| 256 canvas->FillRectInt(SK_ColorBLUE, 0, 0, width(), height()); |
| 257 } |
| 258 |
| 259 void TabStrip2::PaintChildren(gfx::Canvas* canvas) { |
| 260 // Paint the tabs in reverse order, so they stack to the left. |
| 261 Tab2* selected_tab = NULL; |
| 262 for (int i = GetTabCount() - 1; i >= 0; --i) { |
| 263 Tab2* tab = GetTabAt(i); |
| 264 // We must ask the _Tab's_ model, not ourselves, because in some situations |
| 265 // the model will be different to this object, e.g. when a Tab is being |
| 266 // removed after its TabContents has been destroyed. |
| 267 if (!IsSelected(tab)) { |
| 268 tab->ProcessPaint(canvas); |
| 269 } else { |
| 270 selected_tab = tab; |
| 271 } |
| 272 } |
| 273 |
| 274 if (GetWindow()->GetNonClientView()->UseNativeFrame()) { |
| 275 // Make sure unselected tabs are somewhat transparent. |
| 276 SkPaint paint; |
| 277 paint.setColor(SkColorSetARGB(200, 255, 255, 255)); |
| 278 paint.setXfermodeMode(SkXfermode::kDstIn_Mode); |
| 279 paint.setStyle(SkPaint::kFill_Style); |
| 280 canvas->FillRectInt( |
| 281 0, 0, width(), |
| 282 height() - 2, // Visible region that overlaps the toolbar. |
| 283 paint); |
| 284 } |
| 285 |
| 286 // Paint the selected tab last, so it overlaps all the others. |
| 287 if (selected_tab) |
| 288 selected_tab->ProcessPaint(canvas); |
| 289 } |
| 290 |
| 291 //////////////////////////////////////////////////////////////////////////////// |
| 292 // TabStrip2, views::AnimatorDelegate implementation: |
| 293 |
| 294 views::View* TabStrip2::GetClampedView(views::View* host) { |
| 295 int tab_count = GetTabCount(); |
| 296 for (int i = 0; i < tab_count; ++i) { |
| 297 Tab2* tab = GetTabAt(i); |
| 298 if (tab == host && i > 0) |
| 299 return GetTabAt(i - 1); |
| 300 } |
| 301 return NULL; |
| 302 } |
| 303 |
| 304 void TabStrip2::AnimationCompletedForHost(View* host) { |
| 305 Tab2* tab = static_cast<Tab2*>(host); |
| 306 if (tab->removing()) { |
| 307 tabs_.erase(find(tabs_.begin(), tabs_.end(), tab)); |
| 308 RemoveChildView(tab); |
| 309 delete tab; |
| 310 } |
| 311 } |
| 312 |
| 313 //////////////////////////////////////////////////////////////////////////////// |
| 314 // TabStrip2, private: |
| 315 |
| 316 int TabStrip2::GetAnimateFlagsForLayoutSource(LayoutSource source) const { |
| 317 switch (source) { |
| 318 case LS_TAB_ADD: |
| 319 case LS_TAB_SELECT: |
| 320 case LS_TAB_REMOVE: |
| 321 return views::Animator::ANIMATE_WIDTH | views::Animator::ANIMATE_X | |
| 322 views::Animator::ANIMATE_CLAMP; |
| 323 case LS_TAB_DRAG_REORDER: |
| 324 case LS_TAB_DRAG_NORMALIZE: |
| 325 return views::Animator::ANIMATE_X; |
| 326 } |
| 327 DCHECK(source == LS_OTHER); |
| 328 return views::Animator::ANIMATE_NONE; |
| 329 } |
| 330 |
| 331 void TabStrip2::LayoutImpl(LayoutSource source) { |
| 332 int child_count = GetTabCount(); |
| 333 if (child_count > 0) { |
| 334 int child_width = width() / child_count; |
| 335 child_width = std::min(child_width, Tab2::GetStandardSize().width()); |
| 336 |
| 337 int animate_flags = GetAnimateFlagsForLayoutSource(source); |
| 338 int removing_count = 0; |
| 339 for (int i = 0; i < child_count; ++i) { |
| 340 Tab2* tab = GetTabAt(i); |
| 341 if (tab->removing()) |
| 342 ++removing_count; |
| 343 if (!tab->dragging()) { |
| 344 int tab_x = i * child_width - removing_count * child_width; |
| 345 int tab_width = tab->removing() ? 0 : child_width; |
| 346 gfx::Rect new_bounds(tab_x, 0, tab_width, height()); |
| 347 |
| 348 // Tabs that are currently being removed can have their bounds reset |
| 349 // when another tab in the tabstrip is removed before their remove |
| 350 // animation completes. Before they are given a new target bounds to |
| 351 // animate to, we need to unset the removing property so that they are |
| 352 // not pre-emptively deleted. |
| 353 bool removing = tab->removing(); |
| 354 tab->set_removing(false); |
| 355 tab->GetAnimator()->AnimateToBounds(new_bounds, animate_flags); |
| 356 // Now restore the removing property. |
| 357 tab->set_removing(removing); |
| 358 } |
| 359 } |
| 360 } |
| 361 } |
| 362 |
| 363 void TabStrip2::DragDetachTabImpl(Tab2* tab, int index) { |
| 364 gfx::Rect tab_bounds = tab->bounds(); |
| 365 |
| 366 // Determine the origin of the new window. We start with the current mouse |
| 367 // position: |
| 368 gfx::Point new_window_origin(views::Screen::GetCursorScreenPoint()); |
| 369 // Subtract the offset of the mouse pointer from the tab top left when the |
| 370 // drag action began. |
| 371 new_window_origin.Offset(-mouse_tab_offset_.x(), -mouse_tab_offset_.y()); |
| 372 // Subtract the offset of the tab's current position from the window. |
| 373 gfx::Point tab_window_origin; |
| 374 View::ConvertPointToWidget(tab, &tab_window_origin); |
| 375 new_window_origin.Offset(-tab_window_origin.x(), -tab_window_origin.y()); |
| 376 |
| 377 // The new window is created with the same size as the source window but at |
| 378 // the origin calculated above. |
| 379 gfx::Rect new_window_bounds = GetWindow()->GetBounds(); |
| 380 new_window_bounds.set_origin(new_window_origin); |
| 381 |
| 382 model_->DetachTabAt(index, new_window_bounds, tab_bounds); |
| 383 } |
| 384 |
| 385 void TabStrip2::StartDragTabImpl(int index, const gfx::Rect& tab_bounds) { |
| 386 SetDraggedTabBounds(index, tab_bounds); |
| 387 gfx::Rect tab_local_bounds(tab_bounds); |
| 388 tab_local_bounds.set_origin(gfx::Point()); |
| 389 GetWidget()->GenerateMousePressedForView(GetTabAt(index), |
| 390 tab_local_bounds.CenterPoint()); |
| 391 } |
| 392 |
| 393 int TabStrip2::GetInternalIndex(int public_index) const { |
| 394 std::vector<Tab2*>::const_iterator it; |
| 395 int internal_index = public_index; |
| 396 int valid_tab_count = 0; |
| 397 for (it = tabs_.begin(); it != tabs_.end(); ++it) { |
| 398 if (public_index >= valid_tab_count) |
| 399 break; |
| 400 if ((*it)->removing()) |
| 401 ++internal_index; |
| 402 else |
| 403 ++valid_tab_count; |
| 404 } |
| 405 return internal_index; |
| 406 } |
OLD | NEW |