Index: chrome/browser/views/tabs/tab_strip_2.cc |
=================================================================== |
--- chrome/browser/views/tabs/tab_strip_2.cc (revision 0) |
+++ chrome/browser/views/tabs/tab_strip_2.cc (revision 0) |
@@ -0,0 +1,406 @@ |
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this |
+// source code is governed by a BSD-style license that can be found in the |
+// LICENSE file. |
+ |
+#include "chrome/browser/views/tabs/tab_strip_2.h" |
+ |
+#include "app/gfx/canvas.h" |
+#include "app/slide_animation.h" |
+#include "app/win_util.h" |
+#include "base/command_line.h" |
+#include "base/message_loop.h" |
+#include "chrome/common/chrome_switches.h" |
+#include "views/animator.h" |
+#include "views/screen.h" |
+#include "views/widget/widget.h" |
+#include "views/window/non_client_view.h" |
+#include "views/window/window.h" |
+ |
+static const int kHorizontalMoveThreshold = 16; // pixels |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// TabStrip2, public: |
+ |
+TabStrip2::TabStrip2(TabStrip2Model* model) |
+ : model_(model), |
+ last_move_screen_x_(0), |
+ detach_factory_(this), |
+ drag_start_factory_(this) { |
+} |
+ |
+TabStrip2::~TabStrip2() { |
+} |
+ |
+// static |
+bool TabStrip2::Enabled() { |
+ return CommandLine::ForCurrentProcess()->HasSwitch( |
+ switches::kEnableTabtastic2); |
+} |
+ |
+void TabStrip2::AddTabAt(int index) { |
+ Tab2* tab = new Tab2(this); |
+ int insertion_index = GetInternalIndex(index); |
+ tabs_.insert(tabs_.begin() + insertion_index, tab); |
+ AddChildView(insertion_index, tab); |
+ LayoutImpl(LS_TAB_ADD); |
+} |
+ |
+void TabStrip2::RemoveTabAt(int index, Tab2Model* removing_model) { |
+ Tab2* tab = GetTabAt(GetInternalIndex(index)); |
+ |
+ DCHECK(!tab->removing()); |
+ tab->set_removing(true); |
+ |
+ DCHECK(removing_model); |
+ tab->SetRemovingModel(removing_model); |
+ |
+ LayoutImpl(LS_TAB_REMOVE); |
+} |
+ |
+void TabStrip2::SelectTabAt(int index) { |
+ LayoutImpl(LS_TAB_SELECT); |
+ SchedulePaint(); |
+} |
+ |
+void TabStrip2::MoveTabAt(int index, int to_index) { |
+ int from_index = GetInternalIndex(index); |
+ Tab2* tab = GetTabAt(from_index); |
+ tabs_.erase(tabs_.begin() + from_index); |
+ tabs_.insert(tabs_.begin() + GetInternalIndex(to_index), tab); |
+ LayoutImpl(LS_TAB_DRAG_REORDER); |
+} |
+ |
+int TabStrip2::GetTabCount() const { |
+ return tabs_.size(); |
+} |
+ |
+Tab2* TabStrip2::GetTabAt(int index) const { |
+ return tabs_.at(index); |
+} |
+ |
+int TabStrip2::GetTabIndex(Tab2* tab) const { |
+ std::vector<Tab2*>::const_iterator it = find(tabs_.begin(), tabs_.end(), tab); |
+ if (it != tabs_.end()) |
+ return it - tabs_.begin(); |
+ return -1; |
+} |
+ |
+int TabStrip2::GetInsertionIndexForPoint(const gfx::Point& point) const { |
+ int tab_count = GetTabCount(); |
+ for (int i = 0; i < tab_count; ++i) { |
+ if (GetTabAt(i)->removing()) |
+ continue; |
+ gfx::Rect tab_bounds = GetTabAt(i)->bounds(); |
+ gfx::Rect tab_left_half = tab_bounds; |
+ tab_left_half.set_width(tab_left_half.width() / 2); |
+ if (point.x() >= tab_left_half.x() && point.x() <= tab_left_half.right()) |
+ return i; |
+ gfx::Rect tab_right_half = tab_bounds; |
+ tab_right_half.set_x(tab_right_half.width() / 2); |
+ tab_right_half.set_width(tab_right_half.x()); |
+ if (point.x() > tab_right_half.x() && point.x() <= tab_right_half.right()) |
+ if (tab_right_half.Contains(point)) |
+ return i + 1; |
+ } |
+ return tab_count; |
+} |
+ |
+gfx::Rect TabStrip2::GetDraggedTabScreenBounds(const gfx::Point& screen_point) { |
+ gfx::Point tab_screen_origin(screen_point); |
+ tab_screen_origin.Offset(mouse_tab_offset_.x(), mouse_tab_offset_.y()); |
+ return gfx::Rect(tab_screen_origin, GetTabAt(0)->bounds().size()); |
+} |
+ |
+void TabStrip2::SetDraggedTabBounds(int index, const gfx::Rect& tab_bounds) { |
+ // This function should only ever be called goats |
+ Tab2* dragged_tab = GetTabAt(index); |
+ dragged_tab->SetBounds(tab_bounds); |
+ SchedulePaint(); |
+} |
+ |
+void TabStrip2::SendDraggedTabHome() { |
+ LayoutImpl(LS_TAB_DRAG_REORDER); |
+} |
+ |
+void TabStrip2::ResumeDraggingTab(int index, const gfx::Rect& tab_bounds) { |
+ MessageLoop::current()->PostTask(FROM_HERE, |
+ drag_start_factory_.NewRunnableMethod(&TabStrip2::StartDragTabImpl, index, |
+ tab_bounds)); |
+} |
+ |
+// static |
+bool TabStrip2::IsDragRearrange(TabStrip2* tabstrip, |
+ const gfx::Point& screen_point) { |
+ gfx::Point origin; |
+ View::ConvertPointToScreen(tabstrip, &origin); |
+ gfx::Rect tabstrip_bounds_in_screen_coords(origin, tabstrip->bounds().size()); |
+ if (tabstrip_bounds_in_screen_coords.Contains(screen_point)) |
+ return true; |
+ |
+ // The tab is only detached if the tab is moved outside the bounds of the |
+ // TabStrip to the left or right, or a certain distance above or below the |
+ // TabStrip defined by the vertical detach magnetism below. This is to |
+ // prevent accidental detaches when rearranging horizontally. |
+ static const int kVerticalDetachMagnetism = 45; |
+ |
+ bool rearrange = true; |
+ if (screen_point.x() < tabstrip_bounds_in_screen_coords.right() && |
+ screen_point.x() >= tabstrip_bounds_in_screen_coords.x()) { |
+ int lower_threshold = |
+ tabstrip_bounds_in_screen_coords.bottom() + kVerticalDetachMagnetism; |
+ int upper_threshold = |
+ tabstrip_bounds_in_screen_coords.y() - kVerticalDetachMagnetism; |
+ return screen_point.y() >= upper_threshold && |
+ screen_point.y() <= lower_threshold; |
+ } |
+ return false; |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// TabStrip2, Tab2Model implementation: |
+ |
+string16 TabStrip2::GetTitle(Tab2* tab) const { |
+ return model_->GetTitle(GetTabIndex(tab)); |
+} |
+ |
+bool TabStrip2::IsSelected(Tab2* tab) const { |
+ return model_->IsSelected(GetTabIndex(tab)); |
+} |
+ |
+void TabStrip2::SelectTab(Tab2* tab) { |
+ model_->SelectTabAt(GetTabIndex(tab)); |
+} |
+ |
+void TabStrip2::CaptureDragInfo(Tab2* tab, |
+ const views::MouseEvent& drag_event) { |
+ mouse_tab_offset_ = drag_event.location(); |
+} |
+ |
+bool TabStrip2::DragTab(Tab2* tab, const views::MouseEvent& drag_event) { |
+ if (!model_->CanDragTabs()) |
+ return false; |
+ |
+ int tab_x = tab->x() + drag_event.location().x() - mouse_tab_offset_.x(); |
+ if (tab_x < 0) |
+ tab_x = 0; |
+ if ((tab_x + tab->width()) > bounds().right()) |
+ tab_x = bounds().right() - tab_x - tab->width(); |
+ tab->SetBounds(tab_x, tab->y(), tab->width(), tab->height()); |
+ SchedulePaint(); |
+ |
+ int tab_index = GetTabIndex(tab); |
+ int dest_index = tab_index; |
+ |
+ Tab2* next_tab = NULL; |
+ Tab2* prev_tab = NULL; |
+ int next_tab_index = tab_index + 1; |
+ if (next_tab_index < GetTabCount()) |
+ next_tab = GetTabAt(next_tab_index); |
+ int prev_tab_index = tab_index - 1; |
+ if (prev_tab_index >= 0) |
+ prev_tab = GetTabAt(prev_tab_index); |
+ |
+ if (next_tab) { |
+ int next_tab_middle_x = next_tab->x() + next_tab->bounds().width() / 2; |
+ if (!next_tab->IsAnimating() && tab->bounds().right() > next_tab_middle_x) |
+ ++dest_index; |
+ } |
+ if (prev_tab) { |
+ int prev_tab_middle_x = prev_tab->x() + prev_tab->bounds().width() / 2; |
+ if (!prev_tab->IsAnimating() && tab->bounds().x() < prev_tab_middle_x) |
+ --dest_index; |
+ } |
+ |
+ gfx::Point screen_point = views::Screen::GetCursorScreenPoint(); |
+ if (IsDragRearrange(this, screen_point)) { |
+ if (abs(screen_point.x() - last_move_screen_x_) > |
+ kHorizontalMoveThreshold) { |
+ if (dest_index != tab_index) { |
+ last_move_screen_x_ = screen_point.x(); |
+ model_->MoveTabAt(tab_index, dest_index); |
+ } |
+ } |
+ } else { |
+ // We're going to detach. We need to release mouse capture so that further |
+ // mouse events will be sent to the appropriate window (the detached window) |
+ // and so that we don't recursively create nested message loops (dragging |
+ // is done by windows in a nested message loop). |
+ ReleaseCapture(); |
+ MessageLoop::current()->PostTask(FROM_HERE, |
+ detach_factory_.NewRunnableMethod(&TabStrip2::DragDetachTabImpl, |
+ tab, tab_index)); |
+ } |
+ return true; |
+} |
+ |
+void TabStrip2::DragEnded(Tab2* tab) { |
+ LayoutImpl(LS_TAB_DRAG_NORMALIZE); |
+} |
+ |
+views::AnimatorDelegate* TabStrip2::AsAnimatorDelegate() { |
+ return this; |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// TabStrip2, views::View overrides: |
+ |
+gfx::Size TabStrip2::GetPreferredSize() { |
+ return gfx::Size(0, 27); |
+} |
+ |
+void TabStrip2::Layout() { |
+ LayoutImpl(LS_OTHER); |
+} |
+ |
+void TabStrip2::Paint(gfx::Canvas* canvas) { |
+ canvas->FillRectInt(SK_ColorBLUE, 0, 0, width(), height()); |
+} |
+ |
+void TabStrip2::PaintChildren(gfx::Canvas* canvas) { |
+ // Paint the tabs in reverse order, so they stack to the left. |
+ Tab2* selected_tab = NULL; |
+ for (int i = GetTabCount() - 1; i >= 0; --i) { |
+ Tab2* tab = GetTabAt(i); |
+ // We must ask the _Tab's_ model, not ourselves, because in some situations |
+ // the model will be different to this object, e.g. when a Tab is being |
+ // removed after its TabContents has been destroyed. |
+ if (!IsSelected(tab)) { |
+ tab->ProcessPaint(canvas); |
+ } else { |
+ selected_tab = tab; |
+ } |
+ } |
+ |
+ if (GetWindow()->GetNonClientView()->UseNativeFrame()) { |
+ // Make sure unselected tabs are somewhat transparent. |
+ SkPaint paint; |
+ paint.setColor(SkColorSetARGB(200, 255, 255, 255)); |
+ paint.setXfermodeMode(SkXfermode::kDstIn_Mode); |
+ paint.setStyle(SkPaint::kFill_Style); |
+ canvas->FillRectInt( |
+ 0, 0, width(), |
+ height() - 2, // Visible region that overlaps the toolbar. |
+ paint); |
+ } |
+ |
+ // Paint the selected tab last, so it overlaps all the others. |
+ if (selected_tab) |
+ selected_tab->ProcessPaint(canvas); |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// TabStrip2, views::AnimatorDelegate implementation: |
+ |
+views::View* TabStrip2::GetClampedView(views::View* host) { |
+ int tab_count = GetTabCount(); |
+ for (int i = 0; i < tab_count; ++i) { |
+ Tab2* tab = GetTabAt(i); |
+ if (tab == host && i > 0) |
+ return GetTabAt(i - 1); |
+ } |
+ return NULL; |
+} |
+ |
+void TabStrip2::AnimationCompletedForHost(View* host) { |
+ Tab2* tab = static_cast<Tab2*>(host); |
+ if (tab->removing()) { |
+ tabs_.erase(find(tabs_.begin(), tabs_.end(), tab)); |
+ RemoveChildView(tab); |
+ delete tab; |
+ } |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// TabStrip2, private: |
+ |
+int TabStrip2::GetAnimateFlagsForLayoutSource(LayoutSource source) const { |
+ switch (source) { |
+ case LS_TAB_ADD: |
+ case LS_TAB_SELECT: |
+ case LS_TAB_REMOVE: |
+ return views::Animator::ANIMATE_WIDTH | views::Animator::ANIMATE_X | |
+ views::Animator::ANIMATE_CLAMP; |
+ case LS_TAB_DRAG_REORDER: |
+ case LS_TAB_DRAG_NORMALIZE: |
+ return views::Animator::ANIMATE_X; |
+ } |
+ DCHECK(source == LS_OTHER); |
+ return views::Animator::ANIMATE_NONE; |
+} |
+ |
+void TabStrip2::LayoutImpl(LayoutSource source) { |
+ int child_count = GetTabCount(); |
+ if (child_count > 0) { |
+ int child_width = width() / child_count; |
+ child_width = std::min(child_width, Tab2::GetStandardSize().width()); |
+ |
+ int animate_flags = GetAnimateFlagsForLayoutSource(source); |
+ int removing_count = 0; |
+ for (int i = 0; i < child_count; ++i) { |
+ Tab2* tab = GetTabAt(i); |
+ if (tab->removing()) |
+ ++removing_count; |
+ if (!tab->dragging()) { |
+ int tab_x = i * child_width - removing_count * child_width; |
+ int tab_width = tab->removing() ? 0 : child_width; |
+ gfx::Rect new_bounds(tab_x, 0, tab_width, height()); |
+ |
+ // Tabs that are currently being removed can have their bounds reset |
+ // when another tab in the tabstrip is removed before their remove |
+ // animation completes. Before they are given a new target bounds to |
+ // animate to, we need to unset the removing property so that they are |
+ // not pre-emptively deleted. |
+ bool removing = tab->removing(); |
+ tab->set_removing(false); |
+ tab->GetAnimator()->AnimateToBounds(new_bounds, animate_flags); |
+ // Now restore the removing property. |
+ tab->set_removing(removing); |
+ } |
+ } |
+ } |
+} |
+ |
+void TabStrip2::DragDetachTabImpl(Tab2* tab, int index) { |
+ gfx::Rect tab_bounds = tab->bounds(); |
+ |
+ // Determine the origin of the new window. We start with the current mouse |
+ // position: |
+ gfx::Point new_window_origin(views::Screen::GetCursorScreenPoint()); |
+ // Subtract the offset of the mouse pointer from the tab top left when the |
+ // drag action began. |
+ new_window_origin.Offset(-mouse_tab_offset_.x(), -mouse_tab_offset_.y()); |
+ // Subtract the offset of the tab's current position from the window. |
+ gfx::Point tab_window_origin; |
+ View::ConvertPointToWidget(tab, &tab_window_origin); |
+ new_window_origin.Offset(-tab_window_origin.x(), -tab_window_origin.y()); |
+ |
+ // The new window is created with the same size as the source window but at |
+ // the origin calculated above. |
+ gfx::Rect new_window_bounds = GetWindow()->GetBounds(); |
+ new_window_bounds.set_origin(new_window_origin); |
+ |
+ model_->DetachTabAt(index, new_window_bounds, tab_bounds); |
+} |
+ |
+void TabStrip2::StartDragTabImpl(int index, const gfx::Rect& tab_bounds) { |
+ SetDraggedTabBounds(index, tab_bounds); |
+ gfx::Rect tab_local_bounds(tab_bounds); |
+ tab_local_bounds.set_origin(gfx::Point()); |
+ GetWidget()->GenerateMousePressedForView(GetTabAt(index), |
+ tab_local_bounds.CenterPoint()); |
+} |
+ |
+int TabStrip2::GetInternalIndex(int public_index) const { |
+ std::vector<Tab2*>::const_iterator it; |
+ int internal_index = public_index; |
+ int valid_tab_count = 0; |
+ for (it = tabs_.begin(); it != tabs_.end(); ++it) { |
+ if (public_index >= valid_tab_count) |
+ break; |
+ if ((*it)->removing()) |
+ ++internal_index; |
+ else |
+ ++valid_tab_count; |
+ } |
+ return internal_index; |
+} |
Property changes on: chrome\browser\views\tabs\tab_strip_2.cc |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |