summaryrefslogtreecommitdiffstats
path: root/chrome/browser/views/tabs
diff options
context:
space:
mode:
authorbeng@google.com <beng@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-12 22:44:06 +0000
committerbeng@google.com <beng@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-12 22:44:06 +0000
commit3146282c2250c51569d971234fbc9756b4d7a2d4 (patch)
tree0f9f418993163cd7a67955d10e89928b28787978 /chrome/browser/views/tabs
parent5db2a6ed0d09ef82cf84ba2677ecfcdff6192c79 (diff)
downloadchromium_src-3146282c2250c51569d971234fbc9756b4d7a2d4.zip
chromium_src-3146282c2250c51569d971234fbc9756b4d7a2d4.tar.gz
chromium_src-3146282c2250c51569d971234fbc9756b4d7a2d4.tar.bz2
Move View components of the Browser's tab strip into the browser_views project, and into the views/ subdirectory on disk.
B=2198 Review URL: http://codereview.chromium.org/3020 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@2140 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/views/tabs')
-rw-r--r--chrome/browser/views/tabs/dragged_tab_controller.cc798
-rw-r--r--chrome/browser/views/tabs/dragged_tab_controller.h286
-rw-r--r--chrome/browser/views/tabs/dragged_tab_view.cc244
-rw-r--r--chrome/browser/views/tabs/dragged_tab_view.h119
-rw-r--r--chrome/browser/views/tabs/hwnd_photobooth.cc159
-rw-r--r--chrome/browser/views/tabs/hwnd_photobooth.h62
-rw-r--r--chrome/browser/views/tabs/tab.cc241
-rw-r--r--chrome/browser/views/tabs/tab.h125
-rw-r--r--chrome/browser/views/tabs/tab_dragging_test.cc507
-rw-r--r--chrome/browser/views/tabs/tab_renderer.cc691
-rw-r--r--chrome/browser/views/tabs/tab_renderer.h174
-rw-r--r--chrome/browser/views/tabs/tab_strip.cc1524
-rw-r--r--chrome/browser/views/tabs/tab_strip.h373
13 files changed, 5303 insertions, 0 deletions
diff --git a/chrome/browser/views/tabs/dragged_tab_controller.cc b/chrome/browser/views/tabs/dragged_tab_controller.cc
new file mode 100644
index 0000000..81b5386
--- /dev/null
+++ b/chrome/browser/views/tabs/dragged_tab_controller.cc
@@ -0,0 +1,798 @@
+// Copyright (c) 2006-2008 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 <math.h>
+
+#include "chrome/browser/views/tabs/dragged_tab_controller.h"
+
+#include "chrome/browser/browser_window.h"
+#include "chrome/browser/frame_util.h"
+#include "chrome/browser/tab_contents.h"
+#include "chrome/browser/views/tabs/dragged_tab_view.h"
+#include "chrome/browser/views/tabs/hwnd_photobooth.h"
+#include "chrome/browser/views/tabs/tab.h"
+#include "chrome/browser/views/tabs/tab_strip.h"
+#include "chrome/browser/web_contents.h"
+#include "chrome/views/event.h"
+#include "chrome/views/root_view.h"
+#include "skia/include/SkBitmap.h"
+
+static const int kHorizontalMoveThreshold = 16; // pixels
+
+namespace {
+
+///////////////////////////////////////////////////////////////////////////////
+// WindowFinder
+// A WindowForPoint facility that can ignore 2 provided window HWNDs.
+//
+class WindowFinder {
+ public:
+ static HWND WindowForPoint(const gfx::Point& screen_point, HWND ignore1) {
+ WindowFinder instance(screen_point, ignore1);
+ return instance.GetResult();
+ }
+ private:
+ WindowFinder(const gfx::Point& screen_point, HWND ignore1)
+ : screen_point_(screen_point.ToPOINT()),
+ ignore1_(ignore1),
+ result_(NULL) {
+ }
+
+ static BOOL CALLBACK WindowEnumProc(HWND hwnd, LPARAM lParam) {
+ WindowFinder* wf = reinterpret_cast<WindowFinder*>(lParam);
+ if (hwnd == wf->ignore1_)
+ return true;
+
+ if (::IsWindowVisible(hwnd)) {
+ CRect r;
+ ::GetWindowRect(hwnd, &r);
+ if (r.PtInRect(wf->screen_point_)) {
+ // We always deal with the root HWND.
+ wf->result_ = GetAncestor(hwnd, GA_ROOT);
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+
+ HWND GetResult() {
+ EnumThreadWindows(GetCurrentThreadId(), WindowEnumProc,
+ reinterpret_cast<LPARAM>(this));
+ return result_;
+ }
+
+ POINT screen_point_;
+ HWND ignore1_;
+ HWND result_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(WindowFinder);
+};
+
+gfx::Point ConvertScreenPointToTabStripPoint(TabStrip* tabstrip,
+ const gfx::Point& screen_point) {
+ CPoint tabstrip_topleft(0, 0);
+ ChromeViews::View::ConvertPointToScreen(tabstrip, &tabstrip_topleft);
+ return gfx::Point(screen_point.x() - tabstrip_topleft.x,
+ screen_point.y() - tabstrip_topleft.y);
+}
+
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabController, public:
+
+DraggedTabController::DraggedTabController(Tab* source_tab,
+ TabStrip* source_tabstrip)
+ : dragged_contents_(NULL),
+ original_delegate_(NULL),
+ source_tab_(source_tab),
+ source_tabstrip_(source_tabstrip),
+ source_model_index_(source_tabstrip->GetIndexOfTab(source_tab)),
+ attached_tabstrip_(source_tabstrip),
+ old_focused_view_(NULL),
+ in_destructor_(false),
+ last_move_screen_x_(0) {
+ ChangeDraggedContents(
+ source_tabstrip_->model()->GetTabContentsAt(source_model_index_));
+ // Listen for Esc key presses.
+ MessageLoopForUI::current()->AddObserver(this);
+}
+
+DraggedTabController::~DraggedTabController() {
+ in_destructor_ = true;
+ CleanUpSourceTab();
+ MessageLoopForUI::current()->RemoveObserver(this);
+ ChangeDraggedContents(NULL); // This removes our observer.
+}
+
+void DraggedTabController::CaptureDragInfo(const gfx::Point& mouse_offset) {
+ start_screen_point_ = GetCursorScreenPoint();
+ mouse_offset_ = mouse_offset;
+}
+
+void DraggedTabController::Drag() {
+ // Before we get to dragging anywhere, ensure that we consider ourselves
+ // attached to the source tabstrip.
+ if (source_tab_->IsVisible() && CanStartDrag())
+ Attach(source_tabstrip_, gfx::Point());
+
+ if (!source_tab_->IsVisible()) {
+ SaveFocus();
+ ContinueDragging();
+ }
+}
+
+void DraggedTabController::EndDrag(bool canceled) {
+ EndDragImpl(canceled ? CANCELED : NORMAL);
+}
+
+Tab* DraggedTabController::GetDragSourceTabForContents(
+ TabContents* contents) const {
+ if (attached_tabstrip_ == source_tabstrip_)
+ return contents == dragged_contents_ ? source_tab_ : NULL;
+ return NULL;
+}
+
+bool DraggedTabController::IsDragSourceTab(Tab* tab) const {
+ return source_tab_ == tab;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabController, PageNavigator implementation:
+
+void DraggedTabController::OpenURLFromTab(
+ TabContents* source,
+ const GURL& url,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition,
+ const std::string& override_encoding) {
+ if (original_delegate_) {
+ if (disposition == CURRENT_TAB)
+ disposition = NEW_WINDOW;
+
+ original_delegate_->OpenURLFromTab(source, url, disposition, transition,
+ override_encoding);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabController, TabContentsDelegate implementation:
+
+void DraggedTabController::NavigationStateChanged(const TabContents* source,
+ unsigned changed_flags) {
+ if (view_.get())
+ view_->Update();
+}
+
+void DraggedTabController::ReplaceContents(TabContents* source,
+ TabContents* new_contents) {
+ DCHECK(dragged_contents_ == source);
+ source->set_delegate(NULL);
+ new_contents->set_delegate(this);
+
+ // If we're attached to a TabStrip, we need to tell the TabStrip that this
+ // TabContents was replaced.
+ if (attached_tabstrip_ && attached_tabstrip_->model() && dragged_contents_) {
+ int index =
+ attached_tabstrip_->model()->GetIndexOfTabContents(dragged_contents_);
+ if (index != TabStripModel::kNoTab)
+ attached_tabstrip_->model()->ReplaceTabContentsAt(index, new_contents);
+ }
+
+ // Update our internal state.
+ ChangeDraggedContents(new_contents);
+
+ if (view_.get())
+ view_->Update();
+}
+
+void DraggedTabController::AddNewContents(TabContents* source,
+ TabContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ // Theoretically could be called while dragging if the page tries to
+ // spawn a window. Route this message back to the browser in most cases.
+ if (disposition == CURRENT_TAB) {
+ ReplaceContents(source, new_contents);
+ } else if (original_delegate_) {
+ original_delegate_->AddNewContents(source, new_contents, disposition,
+ initial_pos, user_gesture);
+ }
+}
+
+void DraggedTabController::ActivateContents(TabContents* contents) {
+ // Ignored.
+}
+
+void DraggedTabController::LoadingStateChanged(TabContents* source) {
+ // It would be nice to respond to this message by changing the
+ // screen shot in the dragged tab.
+ if (view_.get())
+ view_->Update();
+}
+
+void DraggedTabController::CloseContents(TabContents* source) {
+ // Theoretically could be called by a window. Should be ignored
+ // because window.close() is ignored (usually, even though this
+ // method gets called.)
+}
+
+void DraggedTabController::MoveContents(TabContents* source,
+ const gfx::Rect& pos) {
+ // Theoretically could be called by a web page trying to move its
+ // own window. Should be ignored since we're moving the window...
+}
+
+bool DraggedTabController::IsPopup(TabContents* source) {
+ return false;
+}
+
+void DraggedTabController::ToolbarSizeChanged(TabContents* source,
+ bool finished) {
+ // Dragged tabs don't care about this.
+}
+
+void DraggedTabController::URLStarredChanged(TabContents* source,
+ bool starred) {
+ // Ignored.
+}
+
+void DraggedTabController::UpdateTargetURL(TabContents* source,
+ const GURL& url) {
+ // Ignored.
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabController, NotificationObserver implementation:
+
+void DraggedTabController::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NOTIFY_TAB_CONTENTS_DESTROYED);
+ DCHECK(Source<TabContents>(source).ptr() == dragged_contents_);
+ EndDragImpl(TAB_DESTROYED);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabController, MessageLoop::Observer implementation:
+
+void DraggedTabController::WillProcessMessage(const MSG& msg) {
+}
+
+void DraggedTabController::DidProcessMessage(const MSG& msg) {
+ // If the user presses ESC during a drag, we need to abort and revert things
+ // to the way they were. This is the most reliable way to do this since no
+ // single view or window reliably receives events throughout all the various
+ // kinds of tab dragging.
+ if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
+ EndDrag(true);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabController, private:
+
+void DraggedTabController::InitWindowCreatePoint() {
+ CPoint mouse_offset_cpoint(mouse_offset_.x(), mouse_offset_.y());
+ Tab* first_tab = attached_tabstrip_->GetTabAt(0);
+ ChromeViews::View::ConvertPointToViewContainer(first_tab,
+ &mouse_offset_cpoint);
+ window_create_point_.SetPoint(mouse_offset_cpoint.x, mouse_offset_cpoint.y);
+}
+
+gfx::Point DraggedTabController::GetWindowCreatePoint() const {
+ POINT pt;
+ GetCursorPos(&pt);
+ return gfx::Point(pt.x - window_create_point_.x(),
+ pt.y - window_create_point_.y());
+}
+
+void DraggedTabController::ChangeDraggedContents(TabContents* new_contents) {
+ if (dragged_contents_) {
+ NotificationService::current()->RemoveObserver(this,
+ NOTIFY_TAB_CONTENTS_DESTROYED,
+ Source<TabContents>(dragged_contents_));
+ }
+ dragged_contents_ = new_contents;
+ if (dragged_contents_) {
+ NotificationService::current()->AddObserver(this,
+ NOTIFY_TAB_CONTENTS_DESTROYED,
+ Source<TabContents>(dragged_contents_));
+ }
+}
+
+void DraggedTabController::SaveFocus() {
+ if (!old_focused_view_) {
+ old_focused_view_ = source_tab_->GetRootView()->GetFocusedView();
+ source_tab_->GetRootView()->FocusView(source_tab_);
+ }
+}
+
+void DraggedTabController::RestoreFocus() {
+ if (old_focused_view_ && attached_tabstrip_ == source_tabstrip_)
+ old_focused_view_->GetRootView()->FocusView(old_focused_view_);
+ old_focused_view_ = NULL;
+}
+
+bool DraggedTabController::CanStartDrag() const {
+ // Determine if the mouse has moved beyond a minimum elasticity distance in
+ // any direction from the starting point.
+ static const int kMinimumDragDistance = 10;
+ gfx::Point screen_point = GetCursorScreenPoint();
+ int x_offset = abs(screen_point.x() - start_screen_point_.x());
+ int y_offset = abs(screen_point.y() - start_screen_point_.y());
+ return sqrt(pow(static_cast<float>(x_offset), 2) +
+ pow(static_cast<float>(y_offset), 2)) > kMinimumDragDistance;
+}
+
+void DraggedTabController::ContinueDragging() {
+ EnsureDraggedView();
+
+ // Note that the coordinates given to us by |drag_event| are basically
+ // useless, since they're in source_tab_ coordinates. On the surface, you'd
+ // think we could just convert them to screen coordinates, however in the
+ // situation where we're dragging the last tab in a window when multiple
+ // windows are open, the coordinates of |source_tab_| are way off in
+ // hyperspace since the window was moved there instead of being closed so
+ // that we'd keep receiving events. And our ConvertPointToScreen methods
+ // aren't really multi-screen aware. So really it's just safer to get the
+ // actual position of the mouse cursor directly from Windows here, which is
+ // guaranteed to be correct regardless of monitor config.
+ gfx::Point screen_point = GetCursorScreenPoint();
+
+ // Determine whether or not we have dragged over a compatible TabStrip in
+ // another browser window. If we have, we should attach to it and start
+ // dragging within it.
+ TabStrip* target_tabstrip = GetTabStripForPoint(screen_point);
+ if (target_tabstrip != attached_tabstrip_) {
+ if (target_tabstrip) {
+ // We may receive this event before we're fully detached from the source,
+ // we check for that and force a detach now.
+ if (attached_tabstrip_)
+ Detach();
+ Attach(target_tabstrip, screen_point);
+ } else {
+ Detach();
+ }
+ }
+ MoveTab(screen_point);
+}
+
+void DraggedTabController::MoveTab(const gfx::Point& screen_point) {
+ gfx::Point dragged_view_point = GetDraggedViewPoint(screen_point);
+
+ if (attached_tabstrip_) {
+ // Determine the horizontal move threshold. This is dependent on the width
+ // of tabs. The smaller the tabs compared to the standard size, the smaller
+ // the threshold.
+ double unselected, selected;
+ attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected);
+ double ratio = unselected / Tab::GetStandardSize().width();
+ int threshold = static_cast<int>(ratio * kHorizontalMoveThreshold);
+
+ // Update the model, moving the TabContents from one index to another. Do
+ // this only if we have moved a minimum distance since the last reorder (to
+ // prevent jitter).
+ if (abs(screen_point.x() - last_move_screen_x_) > threshold) {
+ TabStripModel* attached_model = attached_tabstrip_->model();
+ int from_index =
+ attached_model->GetIndexOfTabContents(dragged_contents_);
+ gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);
+ int to_index = GetInsertionIndexForDraggedBounds(bounds);
+ to_index = NormalizeIndexToAttachedTabStrip(to_index);
+ if (from_index != to_index) {
+ last_move_screen_x_ = screen_point.x();
+ attached_model->MoveTabContentsAt(from_index, to_index);
+ }
+ }
+ }
+ // Move the View. There are no changes to the model if we're detached.
+ view_->MoveTo(dragged_view_point);
+}
+
+TabStrip* DraggedTabController::GetTabStripForPoint(
+ const gfx::Point& screen_point) const {
+ HWND dragged_hwnd = view_->GetViewContainer()->GetHWND();
+ HWND other_hwnd = WindowFinder::WindowForPoint(screen_point, dragged_hwnd);
+ if (!other_hwnd)
+ return NULL;
+
+ BrowserWindow* other_frame = FrameUtil::GetBrowserWindowForHWND(other_hwnd);
+ if (other_frame) {
+ TabStrip* other_tabstrip = other_frame->GetTabStrip();
+ if (!other_tabstrip->IsCompatibleWith(source_tabstrip_))
+ return NULL;
+ return GetTabStripIfItContains(other_tabstrip, screen_point);
+ }
+ return NULL;
+}
+
+TabStrip* DraggedTabController::GetTabStripIfItContains(
+ TabStrip* tabstrip, const gfx::Point& screen_point) const {
+ static const int kVerticalDetachMagnetism = 15;
+ // Make sure the specified screen point is actually within the bounds of the
+ // specified tabstrip...
+ gfx::Rect tabstrip_bounds = GetViewScreenBounds(tabstrip);
+ if (screen_point.x() < tabstrip_bounds.right() &&
+ screen_point.x() >= tabstrip_bounds.x()) {
+ // TODO(beng): make this be relative to the start position of the mouse for
+ // the source TabStrip.
+ int upper_threshold = tabstrip_bounds.bottom() + kVerticalDetachMagnetism;
+ int lower_threshold = tabstrip_bounds.y() - kVerticalDetachMagnetism;
+ if (screen_point.y() >= lower_threshold &&
+ screen_point.y() <= upper_threshold) {
+ return tabstrip;
+ }
+ }
+ return NULL;
+}
+
+void DraggedTabController::Attach(TabStrip* attached_tabstrip,
+ const gfx::Point& screen_point) {
+ attached_tabstrip_ = attached_tabstrip;
+ InitWindowCreatePoint();
+ attached_tabstrip_->GenerateIdealBounds();
+
+ // We don't need the photo-booth while we're attached.
+ photobooth_.reset(NULL);
+
+ Tab* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
+
+ // Update the View first, so we can ask it for its bounds and determine
+ // where to insert the hidden Tab.
+
+ // If this is the first time Attach is called for this drag, we're attaching
+ // to the source TabStrip, and we should assume the tab count already
+ // includes this Tab since we haven't been detached yet. If we don't do this,
+ // the dragged representation will be a different size to others in the
+ // TabStrip.
+ int tab_count = attached_tabstrip_->GetTabCount();
+ if (!tab)
+ ++tab_count;
+ double unselected_width, selected_width = 0;
+ attached_tabstrip_->GetDesiredTabWidths(tab_count, &unselected_width,
+ &selected_width);
+ EnsureDraggedView();
+ view_->Attach(static_cast<int>(selected_width));
+
+ if (!tab) {
+ // There is no Tab in |attached_tabstrip| that corresponds to the dragged
+ // TabContents. We must now create one.
+
+ // Remove ourselves as the delegate now that the dragged TabContents is
+ // being inserted back into a Browser.
+ dragged_contents_->set_delegate(NULL);
+ original_delegate_ = NULL;
+
+ // Return the TabContents' to normalcy.
+ dragged_contents_->DidCaptureContents();
+
+ // We need to ask the TabStrip we're attached to to ensure that the ideal
+ // bounds for all its tabs are correctly generated, because the calculation
+ // in GetInsertionIndexForDraggedBounds needs them to be to figure out the
+ // appropriate insertion index.
+ attached_tabstrip_->GenerateIdealBounds();
+
+ // Inserting counts as a move. We don't want the tabs to jitter when the
+ // user moves the tab immediately after attaching it.
+ last_move_screen_x_ = screen_point.x();
+
+ // Figure out where to insert the tab based on the bounds of the dragged
+ // representation and the ideal bounds of the other Tabs already in the
+ // strip. ("ideal bounds" are stable even if the Tabs' actual bounds are
+ // changing due to animation).
+ gfx::Rect bounds = GetDraggedViewTabStripBounds(screen_point);
+ int index = GetInsertionIndexForDraggedBounds(bounds);
+ index = std::max(std::min(index, attached_tabstrip_->model()->count()), 0);
+ attached_tabstrip_->model()->InsertTabContentsAt(index, dragged_contents_,
+ true, false);
+
+ tab = GetTabMatchingDraggedContents(attached_tabstrip_);
+ }
+ DCHECK(tab); // We should now have a tab.
+ tab->SetVisible(false);
+
+ // Move the corresponding window to the front.
+ attached_tabstrip_->GetViewContainer()->MoveToFront(true);
+}
+
+void DraggedTabController::Detach() {
+ // Prevent the TabContents' HWND from being hidden by any of the model
+ // operations performed during the drag.
+ dragged_contents_->WillCaptureContents();
+
+ // Update the Model.
+ TabStripModel* attached_model = attached_tabstrip_->model();
+ int index = attached_model->GetIndexOfTabContents(dragged_contents_);
+ if (index >= 0 && index < attached_model->count()) {
+ attached_model->DetachTabContentsAt(index);
+ attached_tabstrip_->SchedulePaint();
+ }
+
+ // If we've removed the last Tab from the TabStrip, hide the frame now.
+ if (attached_model->empty())
+ HideFrame();
+
+ // Set up the photo booth to start capturing the contents of the dragged
+ // TabContents.
+ if (!photobooth_.get())
+ photobooth_.reset(new HWNDPhotobooth(dragged_contents_->GetContainerHWND()));
+
+ // Update the View.
+ view_->Detach(photobooth_.get());
+
+ // We need to be the delegate so we receive messages about stuff,
+ // otherwise our dragged_contents() may be replaced and subsequently
+ // collected/destroyed while the drag is in process, leading to
+ // nasty crashes.
+ original_delegate_ = dragged_contents_->delegate();
+ dragged_contents_->set_delegate(this);
+
+ attached_tabstrip_ = NULL;
+}
+
+int DraggedTabController::GetInsertionIndexForDraggedBounds(
+ const gfx::Rect& dragged_bounds) const {
+ int right_tab_x = 0;
+
+ // If the UI layout of the tab strip is right-to-left, we need to mirror the
+ // bounds of the dragged tab before performing the drag/drop related
+ // calculations. We mirror the dragged bounds because we determine the
+ // position of each tab on the tab strip by calling GetBounds() (without the
+ // mirroring transformation flag) which effectively means that even though
+ // the tabs are rendered from right to left, the code performs the
+ // calculation as if the tabs are laid out from left to right. Mirroring the
+ // dragged bounds adjusts the coordinates of the tab we are dragging so that
+ // it uses the same orientation used by the tabs on the tab strip.
+ gfx::Rect adjusted_bounds(dragged_bounds);
+ adjusted_bounds.set_x(
+ attached_tabstrip_->MirroredLeftPointForRect(adjusted_bounds));
+
+ for (int i = 0; i < attached_tabstrip_->GetTabCount(); ++i) {
+ gfx::Rect ideal_bounds = attached_tabstrip_->GetIdealBounds(i);
+ gfx::Rect left_half = ideal_bounds;
+ left_half.set_width(left_half.width() / 2);
+ gfx::Rect right_half = ideal_bounds;
+ right_half.set_width(ideal_bounds.width() - left_half.width());
+ right_half.set_x(left_half.right());
+ right_tab_x = right_half.right();
+ if (adjusted_bounds.x() >= right_half.x() &&
+ adjusted_bounds.x() < right_half.right()) {
+ return i + 1;
+ } else if (adjusted_bounds.x() >= left_half.x() &&
+ adjusted_bounds.x() < left_half.right()) {
+ return i;
+ }
+ }
+ if (adjusted_bounds.right() > right_tab_x)
+ return attached_tabstrip_->model()->count();
+ return TabStripModel::kNoTab;
+}
+
+gfx::Rect DraggedTabController::GetDraggedViewTabStripBounds(
+ const gfx::Point& screen_point) {
+ gfx::Point client_point =
+ ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point);
+ gfx::Size view_size = view_->attached_tab_size();
+ return gfx::Rect(client_point.x(), client_point.y(),
+ view_size.width(), view_size.height());
+}
+
+gfx::Point DraggedTabController::GetDraggedViewPoint(
+ const gfx::Point& screen_point) {
+ int x = screen_point.x() - mouse_offset_.x();
+ int y = screen_point.y() - mouse_offset_.y();
+
+ // If we're not attached, we just use x and y from above.
+ if (attached_tabstrip_) {
+ gfx::Rect tabstrip_bounds = GetViewScreenBounds(attached_tabstrip_);
+ // Snap the dragged Tab to the TabStrip if we are attached, detaching
+ // only when the mouse position (screen_point) exceeds the screen bounds
+ // of the TabStrip.
+ if (x < tabstrip_bounds.x() && screen_point.x() >= tabstrip_bounds.x())
+ x = tabstrip_bounds.x();
+
+ gfx::Size tab_size = view_->attached_tab_size();
+ int vertical_drag_magnetism = tab_size.height() * 2;
+ int vertical_detach_point = tabstrip_bounds.y() - vertical_drag_magnetism;
+ if (y < tabstrip_bounds.y() && screen_point.y() >= vertical_detach_point)
+ y = tabstrip_bounds.y();
+
+ // Make sure the Tab can't be dragged off the right side of the TabStrip
+ // unless the mouse pointer passes outside the bounds of the strip by
+ // clamping the position of the dragged window to the tabstrip width less
+ // the width of one tab until the mouse pointer (screen_point) exceeds the
+ // screen bounds of the TabStrip.
+ int max_x = tabstrip_bounds.right() - tab_size.width();
+ int max_y = tabstrip_bounds.bottom() - tab_size.height();
+ if (x > max_x && screen_point.x() <= tabstrip_bounds.right())
+ x = max_x;
+ if (y > max_y && screen_point.y() <=
+ (tabstrip_bounds.bottom() + vertical_drag_magnetism)) {
+ y = max_y;
+ }
+ }
+ return gfx::Point(x, y);
+}
+
+
+Tab* DraggedTabController::GetTabMatchingDraggedContents(
+ TabStrip* tabstrip) const {
+ int index = tabstrip->model()->GetIndexOfTabContents(dragged_contents_);
+ return index == TabStripModel::kNoTab ? NULL : tabstrip->GetTabAt(index);
+}
+
+void DraggedTabController::EndDragImpl(EndDragType type) {
+ bool destroy_now = true;
+ if (type != TAB_DESTROYED) {
+ // We only finish up the drag if we were actually dragging. If we never
+ // constructed a view, the user just clicked and released and didn't move the
+ // mouse enough to trigger a drag.
+ if (view_.get()) {
+ RestoreFocus();
+ if (type == CANCELED) {
+ RevertDrag();
+ } else {
+ destroy_now = CompleteDrag();
+ }
+ }
+ } else {
+ // If we get here it means the NavigationController is going down. Don't
+ // attempt to do any cleanup other than resetting the delegate (if we're
+ // still the delegate).
+ if (dragged_contents_ && dragged_contents_->delegate() == this)
+ dragged_contents_->set_delegate(NULL);
+ dragged_contents_ = NULL;
+ attached_tabstrip_ = NULL;
+ }
+ // If we're not destroyed now, we'll be destroyed asynchronously later.
+ if (destroy_now)
+ source_tabstrip_->DestroyDragController();
+}
+
+void DraggedTabController::RevertDrag() {
+ // We save this here because code below will modify |attached_tabstrip_|.
+ bool restore_frame = attached_tabstrip_ != source_tabstrip_;
+ if (attached_tabstrip_) {
+ int index = attached_tabstrip_->model()->GetIndexOfTabContents(
+ dragged_contents_);
+ if (attached_tabstrip_ != source_tabstrip_) {
+ // The Tab was inserted into another TabStrip. We need to put it back
+ // into the original one.
+ attached_tabstrip_->model()->DetachTabContentsAt(index);
+ // TODO(beng): (Cleanup) seems like we should use Attach() for this
+ // somehow.
+ attached_tabstrip_ = source_tabstrip_;
+ source_tabstrip_->model()->InsertTabContentsAt(source_model_index_,
+ dragged_contents_, true, false);
+ } else {
+ // The Tab was moved within the TabStrip where the drag was initiated.
+ // Move it back to the starting location.
+ source_tabstrip_->model()->MoveTabContentsAt(index, source_model_index_);
+ }
+ } else {
+ // TODO(beng): (Cleanup) seems like we should use Attach() for this
+ // somehow.
+ attached_tabstrip_ = source_tabstrip_;
+ // The Tab was detached from the TabStrip where the drag began, and has not
+ // been attached to any other TabStrip. We need to put it back into the
+ // source TabStrip.
+ source_tabstrip_->model()->InsertTabContentsAt(source_model_index_,
+ dragged_contents_, true, false);
+ }
+ // If we're not attached to any TabStrip, or attached to some other TabStrip,
+ // we need to restore the bounds of the original TabStrip's frame, in case
+ // it has been hidden.
+ if (restore_frame) {
+ if (!restore_bounds_.IsEmpty()) {
+ HWND frame_hwnd = source_tabstrip_->GetViewContainer()->GetHWND();
+ MoveWindow(frame_hwnd, restore_bounds_.x(), restore_bounds_.y(),
+ restore_bounds_.width(), restore_bounds_.height(), TRUE);
+ }
+ }
+ source_tab_->SetVisible(true);
+}
+
+bool DraggedTabController::CompleteDrag() {
+ bool destroy_immediately = true;
+ if (attached_tabstrip_) {
+ // We don't need to do anything other than make the Tab visible again,
+ // since the dragged View is going away.
+ Tab* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
+ view_->AnimateToBounds(
+ GetViewScreenBounds(tab),
+ NewCallback(this, &DraggedTabController::OnAnimateToBoundsComplete));
+ destroy_immediately = false;
+ } else {
+ // Compel the model to construct a new window for the detached TabContents.
+ source_tabstrip_->model()->TearOffTabContents(
+ dragged_contents_,
+ GetWindowCreatePoint());
+ CleanUpHiddenFrame();
+ }
+
+ return destroy_immediately;
+}
+
+void DraggedTabController::EnsureDraggedView() {
+ if (!view_.get()) {
+ RECT wr;
+ GetWindowRect(dragged_contents_->GetContainerHWND(), &wr);
+
+ view_.reset(new DraggedTabView(dragged_contents_, mouse_offset_,
+ gfx::Size(wr.right - wr.left, wr.bottom - wr.top)));
+ }
+}
+
+gfx::Point DraggedTabController::GetCursorScreenPoint() const {
+ POINT pt;
+ GetCursorPos(&pt);
+ return gfx::Point(pt);
+}
+
+gfx::Rect DraggedTabController::GetViewScreenBounds(
+ ChromeViews::View* view) const {
+ CPoint view_topleft(0, 0);
+ ChromeViews::View::ConvertPointToScreen(view, &view_topleft);
+ CRect view_screen_bounds;
+ view->GetLocalBounds(&view_screen_bounds, true);
+ view_screen_bounds.OffsetRect(view_topleft);
+ return gfx::Rect(view_screen_bounds);
+}
+
+int DraggedTabController::NormalizeIndexToAttachedTabStrip(int index) const {
+ DCHECK(attached_tabstrip_) << "Can only be called when attached!";
+ TabStripModel* attached_model = attached_tabstrip_->model();
+ if (index >= attached_model->count())
+ return attached_model->count() - 1;
+ if (index == TabStripModel::kNoTab)
+ return 0;
+ return index;
+}
+
+void DraggedTabController::HideFrame() {
+ // We don't actually hide the window, rather we just move it way off-screen.
+ // If we actually hide it, we stop receiving drag events.
+ HWND frame_hwnd = source_tabstrip_->GetViewContainer()->GetHWND();
+ RECT wr;
+ GetWindowRect(frame_hwnd, &wr);
+ MoveWindow(frame_hwnd, 0xFFFF, 0xFFFF, wr.right - wr.left,
+ wr.bottom - wr.top, TRUE);
+
+ // We also save the bounds of the window prior to it being moved, so that if
+ // the drag session is aborted we can restore them.
+ restore_bounds_ = gfx::Rect(wr);
+}
+
+void DraggedTabController::CleanUpHiddenFrame() {
+ // If the model we started dragging from is now empty, we must ask the
+ // delegate to close the frame.
+ if (source_tabstrip_->model()->empty())
+ source_tabstrip_->model()->delegate()->CloseFrameAfterDragSession();
+}
+
+void DraggedTabController::CleanUpSourceTab() {
+ // If we were attached to the source TabStrip, source Tab will be in use
+ // as the Tab. If we were detached or attached to another TabStrip, we can
+ // safely remove this item and delete it now.
+ if (attached_tabstrip_ != source_tabstrip_) {
+ source_tabstrip_->DestroyDraggedSourceTab(source_tab_);
+ source_tab_ = NULL;
+ }
+}
+
+void DraggedTabController::OnAnimateToBoundsComplete() {
+ // Sometimes, for some reason, in automation we can be called back on a
+ // detach even though we aren't attached to a TabStrip. Guard against that.
+ if (attached_tabstrip_) {
+ Tab* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
+ if (tab)
+ tab->SetVisible(true);
+ }
+ CleanUpHiddenFrame();
+
+ if (!in_destructor_)
+ source_tabstrip_->DestroyDragController();
+}
+
diff --git a/chrome/browser/views/tabs/dragged_tab_controller.h b/chrome/browser/views/tabs/dragged_tab_controller.h
new file mode 100644
index 0000000..6c6aed6
--- /dev/null
+++ b/chrome/browser/views/tabs/dragged_tab_controller.h
@@ -0,0 +1,286 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef CHROME_BROWSER_VIEWS_TABS_DRAGGED_TAB_CONTROLLER_H_
+#define CHROME_BROWSER_VIEWS_TABS_DRAGGED_TAB_CONTROLLER_H_
+
+#include "base/gfx/rect.h"
+#include "base/message_loop.h"
+#include "chrome/browser/tab_contents_delegate.h"
+#include "chrome/browser/views/tabs/tab_renderer.h"
+#include "chrome/common/notification_service.h"
+
+namespace ChromeViews {
+class MouseEvent;
+class View;
+}
+class DraggedTabView;
+class HWNDPhotobooth;
+class SkBitmap;
+class Tab;
+class TabStrip;
+class TabStripModel;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// DraggedTabController
+//
+// An object that handles a drag session for an individual Tab within a
+// TabStrip. This object is created whenever the mouse is pressed down on a
+// Tab and destroyed when the mouse is released or the drag operation is
+// aborted. The Tab that the user dragged (the "source tab") owns this object
+// and must be the only one to destroy it (via |DestroyDragController|).
+//
+///////////////////////////////////////////////////////////////////////////////
+class DraggedTabController : public TabContentsDelegate,
+ public NotificationObserver,
+ public MessageLoopForUI::Observer{
+ public:
+ DraggedTabController(Tab* source_tab, TabStrip* source_tabstrip);
+ virtual ~DraggedTabController();
+
+ // Capture information needed to be used during a drag session for this
+ // controller's associated source Tab and TabStrip. |mouse_offset| is the
+ // distance of the mouse pointer from the Tab's origin.
+ void CaptureDragInfo(const gfx::Point& mouse_offset);
+
+ // Responds to drag events subsequent to StartDrag. If the mouse moves a
+ // sufficient distance before the mouse is released, a drag session is
+ // initiated.
+ void Drag();
+
+ // Complete the current drag session. If the drag session was canceled
+ // because the user pressed Escape or something interrupted it, |canceled|
+ // is true so the helper can revert the state to the world before the drag
+ // begun.
+ void EndDrag(bool canceled);
+
+ // Retrieve the source Tab if the TabContents specified matches the one being
+ // dragged by this controller, or NULL if the specified TabContents is not
+ // the same as the one being dragged.
+ Tab* GetDragSourceTabForContents(TabContents* contents) const;
+
+ // Returns true if the specified Tab matches the Tab being dragged.
+ bool IsDragSourceTab(Tab* tab) const;
+
+ private:
+ // Enumeration of the ways a drag session can end.
+ enum EndDragType {
+ // Drag session exited normally: the user released the mouse.
+ NORMAL,
+
+ // The drag session was canceled (alt-tab during drag, escape ...)
+ CANCELED,
+
+ // The tab (NavigationController) was destroyed during the drag.
+ TAB_DESTROYED
+ };
+
+ // Overridden from TabContentsDelegate:
+ virtual void OpenURLFromTab(TabContents* source,
+ const GURL& url,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition,
+ const std::string& override_encoding);
+ virtual void NavigationStateChanged(const TabContents* source,
+ unsigned changed_flags);
+ virtual void ReplaceContents(TabContents* source,
+ TabContents* new_contents);
+ virtual void AddNewContents(TabContents* source,
+ TabContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture);
+ virtual void ActivateContents(TabContents* contents);
+ virtual void LoadingStateChanged(TabContents* source);
+ virtual void CloseContents(TabContents* source);
+ virtual void MoveContents(TabContents* source, const gfx::Rect& pos);
+ virtual bool IsPopup(TabContents* source);
+ virtual void ToolbarSizeChanged(TabContents* source, bool is_animating);
+ virtual void URLStarredChanged(TabContents* source, bool starred);
+ virtual void UpdateTargetURL(TabContents* source, const GURL& url);
+
+ // Overridden from NotificationObserver:
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // Overridden from MessageLoop::Observer:
+ virtual void WillProcessMessage(const MSG& msg);
+ virtual void DidProcessMessage(const MSG& msg);
+
+ // Initialize the offset used to calculate the position to create windows
+ // in |GetWindowCreatePoint|.
+ void InitWindowCreatePoint();
+
+ // Returns the point where a detached window should be created given the
+ // current mouse position.
+ gfx::Point GetWindowCreatePoint() const;
+
+ // Replaces the TabContents being dragged with the specified |new_contents|.
+ // This can occur if the active TabContents for the tab being dragged is
+ // replaced, e.g. if a transition from one TabContentsType to another occurs
+ // during the drag.
+ void ChangeDraggedContents(TabContents* new_contents);
+
+ // Saves focus in the window that the drag initiated from. Focus will be
+ // restored appropriately if the drag ends within this same window.
+ void SaveFocus();
+
+ // Restore focus to the View that had focus before the drag was started, if
+ // the drag ends within the same Window as it began.
+ void RestoreFocus();
+
+ // Tests whether the position of the mouse is past a minimum elasticity
+ // threshold required to start a drag.
+ bool CanStartDrag() const;
+
+ // Move the DraggedTabView according to the current mouse screen position,
+ // potentially updating the source and other TabStrips.
+ void ContinueDragging();
+
+ // Handles moving the Tab within a TabStrip as well as updating the View.
+ void MoveTab(const gfx::Point& screen_point);
+
+ // Returns the compatible TabStrip that is under the specified point (screen
+ // coordinates), or NULL if there is none.
+ TabStrip* GetTabStripForPoint(const gfx::Point& screen_point) const;
+
+ // Returns the specified |tabstrip| if it contains the specified point
+ // (screen coordinates), NULL if it does not.
+ TabStrip* GetTabStripIfItContains(TabStrip* tabstrip,
+ const gfx::Point& screen_point) const;
+
+ // Attach the dragged Tab to the specified TabStrip.
+ void Attach(TabStrip* attached_tabstrip, const gfx::Point& screen_point);
+
+ // Detach the dragged Tab from the current TabStrip.
+ void Detach();
+
+ // Returns the index where the dragged TabContents should be inserted into
+ // the attached TabStripModel given the DraggedTabView's bounds
+ // |dragged_bounds| in coordinates relative to the attached TabStrip.
+ int GetInsertionIndexForDraggedBounds(const gfx::Rect& dragged_bounds) const;
+
+ // Retrieve the bounds of the DraggedTabView, relative to the attached
+ // TabStrip, given location of the dragged tab in screen coordinates.
+ gfx::Rect GetDraggedViewTabStripBounds(const gfx::Point& screen_point);
+
+ // Get the position of the dragged tab view relative to the attached tab
+ // strip.
+ gfx::Point GetDraggedViewPoint(const gfx::Point& screen_point);
+
+ // Finds the Tab within the specified TabStrip that corresponds to the
+ // dragged TabContents.
+ Tab* GetTabMatchingDraggedContents(TabStrip* tabstrip) const;
+
+ // Does the work for EndDrag.
+ void EndDragImpl(EndDragType how_end);
+
+ // If the drag was aborted for some reason, this function is called to un-do
+ // the changes made during the drag operation.
+ void RevertDrag();
+
+ // Finishes the drag operation. Returns true if the drag controller should
+ // be destroyed immediately, false otherwise.
+ bool CompleteDrag();
+
+ // Create the DraggedTabView, if it does not yet exist.
+ void EnsureDraggedView();
+
+ // Utility for getting the mouse position in screen coordinates.
+ gfx::Point GetCursorScreenPoint() const;
+
+ // Returns the bounds (in screen coordinates) of the specified View.
+ gfx::Rect GetViewScreenBounds(ChromeViews::View* tabstrip) const;
+
+ // Utility to convert the specified TabStripModel index to something valid
+ // for the attached TabStrip.
+ int NormalizeIndexToAttachedTabStrip(int index) const;
+
+ // Hides the frame for the window that contains the TabStrip the current
+ // drag session was initiated from.
+ void HideFrame();
+
+ // Closes a hidden frame at the end of a drag session.
+ void CleanUpHiddenFrame();
+
+ // Cleans up a source tab that is no longer used.
+ void CleanUpSourceTab();
+
+ // Completes the drag session after the view has animated to its final
+ // position.
+ void OnAnimateToBoundsComplete();
+
+ // The TabContents being dragged. This can get replaced during the drag if
+ // the associated NavigationController is navigated to a different
+ // TabContentsType.
+ TabContents* dragged_contents_;
+
+ // The original TabContentsDelegate of |dragged_contents_|, before it was
+ // detached from the browser window. We store this so that we can forward
+ // certain delegate notifications back to it if we can't handle them locally.
+ TabContentsDelegate* original_delegate_;
+
+ // The Tab that initiated the drag session.
+ Tab* source_tab_;
+
+ // The TabStrip |source_tab_| originated from.
+ TabStrip* source_tabstrip_;
+
+ // This is the index of the |source_tab_| in |source_tabstrip_| when the drag
+ // began. This is used to restore the previous state if the drag is aborted.
+ int source_model_index_;
+
+ // The TabStrip the dragged Tab is currently attached to, or NULL if the
+ // dragged Tab is detached.
+ TabStrip* attached_tabstrip_;
+
+ // The visual representation of the dragged Tab.
+ scoped_ptr<DraggedTabView> view_;
+
+ // The photo-booth the TabContents sits in when the Tab is detached, to
+ // obtain screen shots.
+ scoped_ptr<HWNDPhotobooth> photobooth_;
+
+ // The position of the mouse (in screen coordinates) at the start of the drag
+ // operation. This is used to calculate minimum elasticity before a
+ // DraggedTabView is constructed.
+ gfx::Point start_screen_point_;
+
+ // This is the offset of the mouse from the top left of the Tab where
+ // dragging begun. This is used to ensure that the dragged view is always
+ // positioned at the correct location during the drag, and to ensure that the
+ // detached window is created at the right location.
+ gfx::Point mouse_offset_;
+
+ // A hint to use when positioning new windows created by detaching Tabs. This
+ // is the distance of the mouse from the top left of the dragged tab as if it
+ // were the distance of the mouse from the top left of the first tab in the
+ // attached TabStrip from the top left of the window.
+ gfx::Point window_create_point_;
+
+ // The bounds of the browser window before the last Tab was detached. When
+ // the last Tab is detached, rather than destroying the frame (which would
+ // abort the drag session), the frame is moved off-screen. If the drag is
+ // aborted (e.g. by the user pressing Esc, or capture being lost), the Tab is
+ // attached to the hidden frame and the frame moved back to these bounds.
+ gfx::Rect restore_bounds_;
+
+ // The last view that had focus in the window containing |source_tab_|. This
+ // is saved so that focus can be restored properly when a drag begins and
+ // ends within this same window.
+ ChromeViews::View* old_focused_view_;
+
+ bool in_destructor_;
+
+ // The horizontal position of the mouse cursor in screen coordinates at the
+ // time of the last re-order event.
+ int last_move_screen_x_;
+
+ DISALLOW_COPY_AND_ASSIGN(DraggedTabController);
+};
+
+#endif // CHROME_BROWSER_VIEWS_TABS_DRAGGED_TAB_CONTROLLER_H_
+
diff --git a/chrome/browser/views/tabs/dragged_tab_view.cc b/chrome/browser/views/tabs/dragged_tab_view.cc
new file mode 100644
index 0000000..6e3fead
--- /dev/null
+++ b/chrome/browser/views/tabs/dragged_tab_view.cc
@@ -0,0 +1,244 @@
+// Copyright (c) 2006-2008 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/dragged_tab_view.h"
+
+#include "chrome/common/gfx/chrome_canvas.h"
+#include "chrome/browser/tab_contents.h"
+#include "chrome/browser/tabs/tab_strip_model.h"
+#include "chrome/browser/views/tabs/hwnd_photobooth.h"
+#include "chrome/browser/views/tabs/tab_renderer.h"
+#include "chrome/views/hwnd_view_container.h"
+#include "skia/include/SkShader.h"
+
+const int kTransparentAlpha = 200;
+const int kOpaqueAlpha = 255;
+const int kDragFrameBorderSize = 2;
+const int kTwiceDragFrameBorderSize = 2 * kDragFrameBorderSize;
+const float kScalingFactor = 0.5;
+const int kAnimateToBoundsDurationMs = 150;
+static const SkColor kDraggedTabBorderColor = SkColorSetRGB(103, 129, 162);
+
+////////////////////////////////////////////////////////////////////////////////
+// DraggedTabView, public:
+
+DraggedTabView::DraggedTabView(TabContents* datasource,
+ const gfx::Point& mouse_tab_offset,
+ const gfx::Size& contents_size)
+ : container_(NULL),
+ renderer_(new TabRenderer),
+ attached_(false),
+ mouse_tab_offset_(mouse_tab_offset),
+ attached_tab_size_(TabRenderer::GetMinimumSelectedSize()),
+ photobooth_(NULL),
+ contents_size_(contents_size),
+ close_animation_(this) {
+ SetParentOwned(false);
+
+ renderer_->UpdateData(datasource);
+
+ container_ = new ChromeViews::HWNDViewContainer;
+ container_->set_window_style(WS_POPUP);
+ container_->set_window_ex_style(
+ WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW);
+ container_->set_can_update_layered_window(false);
+ container_->Init(NULL, gfx::Rect(0, 0, 0, 0), false);
+ container_->SetContentsView(this);
+}
+
+DraggedTabView::~DraggedTabView() {
+ if (close_animation_.IsAnimating())
+ close_animation_.Stop();
+ GetParent()->RemoveChildView(this);
+ container_->Close();
+}
+
+void DraggedTabView::MoveTo(const gfx::Point& screen_point) {
+ if (!container_->IsVisible())
+ container_->ShowWindow(SW_SHOWNOACTIVATE);
+
+ int x;
+ if (UILayoutIsRightToLeft() && !attached_) {
+ // On RTL locales, a dragged tab (when it is not attached to a tab strip)
+ // is rendered using a right-to-left orientation so we should calculate the
+ // window position differently.
+ CSize ps;
+ GetPreferredSize(&ps);
+ x = screen_point.x() - ScaleValue(ps.cx) + mouse_tab_offset_.x() +
+ ScaleValue(
+ renderer_->MirroredXCoordinateInsideView(mouse_tab_offset_.x()));
+ } else {
+ x = screen_point.x() + mouse_tab_offset_.x() -
+ ScaleValue(mouse_tab_offset_.x());
+ }
+ int y = screen_point.y() + mouse_tab_offset_.y() -
+ ScaleValue(mouse_tab_offset_.y());
+
+ container_->SetWindowPos(NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
+}
+
+void DraggedTabView::Attach(int selected_width) {
+ attached_ = true;
+ photobooth_ = NULL;
+ attached_tab_size_.set_width(selected_width);
+ container_->SetLayeredAlpha(kOpaqueAlpha);
+ ResizeContainer();
+ Update();
+}
+
+void DraggedTabView::Detach(HWNDPhotobooth* photobooth) {
+ attached_ = false;
+ photobooth_ = photobooth;
+ container_->SetLayeredAlpha(kTransparentAlpha);
+ ResizeContainer();
+ Update();
+}
+
+void DraggedTabView::Update() {
+ container_->set_can_update_layered_window(true);
+ SchedulePaint();
+ container_->PaintNow(CRect());
+ container_->set_can_update_layered_window(false);
+}
+
+void DraggedTabView::AnimateToBounds(const gfx::Rect& bounds,
+ Callback0::Type* callback) {
+ animation_callback_.reset(callback);
+
+ RECT wr;
+ GetWindowRect(GetViewContainer()->GetHWND(), &wr);
+ animation_start_bounds_ = wr;
+ animation_end_bounds_ = bounds;
+
+ close_animation_.SetSlideDuration(kAnimateToBoundsDurationMs);
+ close_animation_.SetTweenType(SlideAnimation::EASE_OUT);
+ if (!close_animation_.IsShowing()) {
+ close_animation_.Reset();
+ close_animation_.Show();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabView, AnimationDelegate implementation:
+
+void DraggedTabView::AnimationProgressed(const Animation* animation) {
+ int delta_x = (animation_end_bounds_.x() - animation_start_bounds_.x());
+ int x = animation_start_bounds_.x() +
+ static_cast<int>(delta_x * animation->GetCurrentValue());
+ int y = animation_end_bounds_.y();
+ container_->SetWindowPos(NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
+}
+
+void DraggedTabView::AnimationEnded(const Animation* animation) {
+ animation_callback_->Run();
+}
+
+void DraggedTabView::AnimationCanceled(const Animation* animation) {
+ AnimationEnded(animation);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabView, ChromeViews::View overrides:
+
+void DraggedTabView::Paint(ChromeCanvas* canvas) {
+ if (attached_) {
+ PaintAttachedTab(canvas);
+ } else {
+ PaintDetachedView(canvas);
+ }
+}
+
+void DraggedTabView::Layout() {
+ CSize ps;
+ GetPreferredSize(&ps);
+ if (attached_) {
+ renderer_->SetBounds(CRect(0, 0, ps.cx, ps.cy));
+ } else {
+ int left = 0;
+ if (UILayoutIsRightToLeft())
+ left = ps.cx - attached_tab_size_.width();
+ renderer_->SetBounds(CRect(left, 0, left + attached_tab_size_.width(),
+ attached_tab_size_.height()));
+ }
+}
+
+void DraggedTabView::GetPreferredSize(CSize* out) {
+ DCHECK(out);
+ if (attached_) {
+ *out = attached_tab_size_.ToSIZE();
+ } else {
+ int width = std::max(attached_tab_size_.width(), contents_size_.width()) +
+ kTwiceDragFrameBorderSize;
+ int height = attached_tab_size_.height() + kDragFrameBorderSize +
+ contents_size_.height();
+ *out = CSize(width, height);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DraggedTabView, private:
+
+void DraggedTabView::PaintAttachedTab(ChromeCanvas* canvas) {
+ renderer_->ProcessPaint(canvas);
+}
+
+void DraggedTabView::PaintDetachedView(ChromeCanvas* canvas) {
+ CSize ps;
+ GetPreferredSize(&ps);
+ ChromeCanvas scale_canvas(ps.cx, ps.cy, false);
+ SkBitmap& bitmap_device = const_cast<SkBitmap&>(
+ scale_canvas.getTopPlatformDevice().accessBitmap(true));
+ bitmap_device.eraseARGB(0, 0, 0, 0);
+
+ scale_canvas.FillRectInt(kDraggedTabBorderColor, 0,
+ attached_tab_size_.height() - kDragFrameBorderSize,
+ ps.cx, ps.cy - attached_tab_size_.height());
+ int image_x = kDragFrameBorderSize;
+ int image_y = attached_tab_size_.height();
+ int image_w = ps.cx - kTwiceDragFrameBorderSize;
+ int image_h =
+ ps.cy - kTwiceDragFrameBorderSize - attached_tab_size_.height();
+ scale_canvas.FillRectInt(SK_ColorBLACK, image_x, image_y, image_w, image_h);
+ photobooth_->PaintScreenshotIntoCanvas(
+ &scale_canvas,
+ gfx::Rect(image_x, image_y, image_w, image_h));
+ renderer_->ProcessPaint(&scale_canvas);
+
+ SkIRect subset;
+ subset.set(0, 0, ps.cx, ps.cy);
+ SkBitmap mipmap = scale_canvas.ExtractBitmap();
+ mipmap.buildMipMap(true);
+
+ SkShader* bitmap_shader =
+ SkShader::CreateBitmapShader(mipmap, SkShader::kClamp_TileMode,
+ SkShader::kClamp_TileMode);
+
+ SkMatrix shader_scale;
+ shader_scale.setScale(kScalingFactor, kScalingFactor);
+ bitmap_shader->setLocalMatrix(shader_scale);
+
+ SkPaint paint;
+ paint.setShader(bitmap_shader);
+ paint.setAntiAlias(true);
+ bitmap_shader->unref();
+
+ SkRect rc;
+ rc.fLeft = 0;
+ rc.fTop = 0;
+ rc.fRight = SkIntToScalar(ps.cx);
+ rc.fBottom = SkIntToScalar(ps.cy);
+ canvas->drawRect(rc, paint);
+}
+
+void DraggedTabView::ResizeContainer() {
+ CSize ps;
+ GetPreferredSize(&ps);
+ SetWindowPos(container_->GetHWND(), HWND_TOPMOST, 0, 0, ScaleValue(ps.cx),
+ ScaleValue(ps.cy), SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
+}
+
+int DraggedTabView::ScaleValue(int value) {
+ return attached_ ? value : static_cast<int>(value * kScalingFactor);
+}
+
diff --git a/chrome/browser/views/tabs/dragged_tab_view.h b/chrome/browser/views/tabs/dragged_tab_view.h
new file mode 100644
index 0000000..e59582c
--- /dev/null
+++ b/chrome/browser/views/tabs/dragged_tab_view.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef CHROME_BROWSER_VIEWS_TABS_DRAGGED_TAB_VIEW_H_
+#define CHROME_BROWSER_VIEWS_TABS_DRAGGED_TAB_VIEW_H_
+
+#include "base/gfx/point.h"
+#include "base/gfx/size.h"
+#include "base/task.h"
+#include "chrome/common/slide_animation.h"
+#include "chrome/views/view.h"
+#include "skia/include/SkBitmap.h"
+
+namespace ChromeViews {
+class HWNDViewContainer;
+}
+namespace gfx {
+class Point;
+}
+class HWNDPhotobooth;
+class Tab;
+class TabContents;
+class TabRenderer;
+
+class DraggedTabView : public ChromeViews::View,
+ public AnimationDelegate {
+ public:
+ DraggedTabView(TabContents* datasource,
+ const gfx::Point& mouse_tab_offset,
+ const gfx::Size& contents_size);
+ virtual ~DraggedTabView();
+
+ // Moves the DraggedTabView to the appropriate location given the mouse
+ // pointer at |screen_point|.
+ void MoveTo(const gfx::Point& screen_point);
+
+ // Notifies the DraggedTabView that it has become attached to a TabStrip.
+ void Attach(int selected_width);
+
+ // Notifies the DraggedTabView that it has been detached from a TabStrip.
+ void Detach(HWNDPhotobooth* photobooth);
+
+ // Notifies the DraggedTabView that it should update itself.
+ void Update();
+
+ // Animates the DraggedTabView to the specified bounds, then calls back to
+ // |callback|.
+ void AnimateToBounds(const gfx::Rect& bounds, Callback0::Type* callback);
+
+ // Returns the size of the DraggedTabView. Used when attaching to a TabStrip
+ // to determine where to place the Tab in the attached TabStrip.
+ gfx::Size attached_tab_size() const { return attached_tab_size_; }
+
+ private:
+ // Overridden from AnimationDelegate:
+ virtual void AnimationProgressed(const Animation* animation);
+ virtual void AnimationEnded(const Animation* animation);
+ virtual void AnimationCanceled(const Animation* animation);
+
+ // Overridden from ChromeViews::View:
+ virtual void Paint(ChromeCanvas* canvas);
+ virtual void Layout();
+ virtual void GetPreferredSize(CSize* out);
+
+ // Paint the view, when it's attached to a TabStrip.
+ void PaintAttachedTab(ChromeCanvas* canvas);
+
+ // Paint the view, when it's not attached to any TabStrip.
+ void PaintDetachedView(ChromeCanvas* canvas);
+
+ // Resizes the container to fit the content for the current attachment mode.
+ void ResizeContainer();
+
+ // Utility for scaling a size by the current scaling factor.
+ int ScaleValue(int value);
+
+ // The window that contains the DraggedTabView.
+ ChromeViews::HWNDViewContainer* container_;
+
+ // The renderer that paints the Tab shape.
+ scoped_ptr<TabRenderer> renderer_;
+
+ // True if the view is currently attached to a TabStrip. Controls rendering
+ // and sizing modes.
+ bool attached_;
+
+ // The unscaled offset of the mouse from the top left of the dragged Tab.
+ // This is used to maintain an appropriate offset for the mouse pointer when
+ // dragging scaled and unscaled representations, and also to calculate the
+ // position of detached windows.
+ gfx::Point mouse_tab_offset_;
+
+ // The desired width of the TabRenderer when the DraggedTabView is attached
+ // to a TabStrip.
+ gfx::Size attached_tab_size_;
+
+ // A handle to the DIB containing the current screenshot of the TabContents
+ // we are dragging.
+ HWNDPhotobooth* photobooth_;
+
+ // The dimensions of the TabContents being dragged.
+ gfx::Size contents_size_;
+
+ // The animation used to slide the attached view to its final location.
+ SlideAnimation close_animation_;
+
+ // A callback notified when the animation is complete.
+ scoped_ptr<Callback0::Type> animation_callback_;
+
+ // The start and end bounds of the animation sequence.
+ gfx::Rect animation_start_bounds_;
+ gfx::Rect animation_end_bounds_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(DraggedTabView);
+};
+
+#endif // CHROME_BROWSER_VIEWS_TABS_DRAGGED_TAB_VIEW_H_
+
diff --git a/chrome/browser/views/tabs/hwnd_photobooth.cc b/chrome/browser/views/tabs/hwnd_photobooth.cc
new file mode 100644
index 0000000..a230c6b
--- /dev/null
+++ b/chrome/browser/views/tabs/hwnd_photobooth.cc
@@ -0,0 +1,159 @@
+// Copyright (c) 2006-2008 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 "base/gfx/point.h"
+#include "chrome/browser/tab_contents.h"
+#include "chrome/browser/views/tabs/hwnd_photobooth.h"
+#include "chrome/common/gfx/chrome_canvas.h"
+#include "chrome/views/hwnd_view_container.h"
+#include "skia/include/SkBitmap.h"
+
+namespace {
+
+static BOOL CALLBACK MonitorEnumProc(HMONITOR monitor, HDC monitor_dc,
+ RECT* monitor_rect, LPARAM data) {
+ gfx::Point* point = reinterpret_cast<gfx::Point*>(data);
+ if (monitor_rect->right > point->x() && monitor_rect->bottom > point->y()) {
+ point->set_x(monitor_rect->right);
+ point->set_y(monitor_rect->bottom);
+ }
+ return TRUE;
+}
+
+gfx::Point GetCaptureWindowPosition() {
+ // Since the capture window must be visible to be painted, it must be opened
+ // off screen to avoid flashing. But if it is opened completely off-screen
+ // (e.g. at 0xFFFFx0xFFFF) then on Windows Vista it will not paint even if it
+ // _is_ visible. So we need to find the right/bottommost monitor, and
+ // position it so that 1x1 pixel is on-screen on that monitor which is enough
+ // to convince Vista to paint it. Don't ask why this is so - this appears to
+ // be a regression over XP.
+ gfx::Point point(0, 0);
+ EnumDisplayMonitors(NULL, NULL, &MonitorEnumProc,
+ reinterpret_cast<LPARAM>(&point));
+ return gfx::Point(point.x() - 1, point.y() - 1);
+}
+
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HWNDPhotobooth, public:
+
+HWNDPhotobooth::HWNDPhotobooth(HWND initial_hwnd)
+ : capture_window_(NULL),
+ current_hwnd_(initial_hwnd) {
+ DCHECK(IsWindow(current_hwnd_));
+ CreateCaptureWindow(initial_hwnd);
+}
+
+HWNDPhotobooth::~HWNDPhotobooth() {
+ // Detach the attached HWND. The creator of the photo-booth is responsible
+ // for destroying it.
+ ReplaceHWND(NULL);
+ capture_window_->Close();
+}
+
+void HWNDPhotobooth::ReplaceHWND(HWND new_hwnd) {
+ if (IsWindow(current_hwnd_) &&
+ GetParent(current_hwnd_) == capture_window_->GetHWND()) {
+ // We need to hide the window too, so it doesn't show up in the TaskBar or
+ // be parented to the desktop.
+ ShowWindow(current_hwnd_, SW_HIDE);
+ SetParent(current_hwnd_, NULL);
+ }
+ current_hwnd_ = new_hwnd;
+
+ if (IsWindow(new_hwnd)) {
+ // Insert the TabContents into the capture window.
+ SetParent(current_hwnd_, capture_window_->GetHWND());
+
+ // Show the window (it may not be visible). This is the only safe way of
+ // doing this. ShowWindow does not work.
+ SetWindowPos(current_hwnd_, NULL, 0, 0, 0, 0,
+ SWP_DEFERERASE | SWP_NOACTIVATE | SWP_NOCOPYBITS |
+ SWP_NOOWNERZORDER | SWP_NOSENDCHANGING | SWP_NOZORDER |
+ SWP_SHOWWINDOW | SWP_NOSIZE);
+ }
+}
+
+void HWNDPhotobooth::PaintScreenshotIntoCanvas(
+ ChromeCanvas* canvas,
+ const gfx::Rect& target_bounds) {
+ // Our contained window may have been re-parented. Make sure it belongs to
+ // us until someone calls ReplaceHWND(NULL).
+ if (IsWindow(current_hwnd_) &&
+ GetParent(current_hwnd_) != capture_window_->GetHWND()) {
+ ReplaceHWND(current_hwnd_);
+ }
+
+ // We compel the contained HWND to paint now, synchronously. We do this to
+ // populate the device context with valid and current data.
+ RedrawWindow(current_hwnd_, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
+
+ // Transfer the contents of the layered capture window to the screen-shot
+ // canvas' DIB.
+ HDC target_dc = canvas->beginPlatformPaint();
+ HDC source_dc = GetDC(current_hwnd_);
+ RECT window_rect = {0};
+ GetWindowRect(current_hwnd_, &window_rect);
+ BitBlt(target_dc, target_bounds.x(), target_bounds.y(),
+ target_bounds.width(), target_bounds.height(), source_dc, 0, 0,
+ SRCCOPY);
+ // Windows screws up the alpha channel on all text it draws, and so we need
+ // to call makeOpaque _after_ the blit to correct for this.
+ canvas->getTopPlatformDevice().makeOpaque(target_bounds.x(),
+ target_bounds.y(),
+ target_bounds.width(),
+ target_bounds.height());
+ ReleaseDC(current_hwnd_, source_dc);
+ canvas->endPlatformPaint();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HWNDPhotobooth, private:
+
+void HWNDPhotobooth::CreateCaptureWindow(HWND initial_hwnd) {
+ // Snapshotting a HWND is tricky - if the HWND is clipped (e.g. positioned
+ // partially off-screen) then just blitting from the HWND' DC to the capture
+ // bitmap would be incorrect, since the capture bitmap would show only the
+ // visible area of the HWND.
+ //
+ // The approach turns out to be to create a second layered window in
+ // hyperspace the to act as a "photo booth." The window is created with the
+ // size of the unclipped HWND, and we attach the HWND as a child, refresh the
+ // HWND' by calling |Paint| on it, and then blitting from the HWND's DC to
+ // the capture bitmap. This results in the entire unclipped HWND display
+ // bitmap being captured.
+ //
+ // The capture window must be layered so that Windows generates a backing
+ // store for it, so that blitting from a child window's DC produces data. If
+ // the window is not layered, because it is off-screen Windows does not
+ // retain its contents and blitting results in blank data. The capture window
+ // is a "basic" (1 level of alpha) layered window because that is the mode
+ // that supports having child windows (variable alpha layered windows do not
+ // support child HWNDs).
+ //
+ // This function sets up the off-screen capture window, and attaches the
+ // associated HWND to it. Note that the details are important here, see below
+ // for further comments.
+ //
+ CRect contents_rect;
+ GetClientRect(initial_hwnd, &contents_rect);
+ gfx::Point window_position = GetCaptureWindowPosition();
+ gfx::Rect capture_bounds(window_position.x(), window_position.y(),
+ contents_rect.Width(), contents_rect.Height());
+ capture_window_ = new ChromeViews::HWNDViewContainer;
+ capture_window_->set_window_style(WS_POPUP);
+ // WS_EX_TOOLWINDOW ensures the capture window doesn't produce a Taskbar
+ // button.
+ capture_window_->set_window_ex_style(WS_EX_LAYERED | WS_EX_TOOLWINDOW);
+ capture_window_->Init(NULL, capture_bounds, false);
+ // If the capture window isn't visible, blitting from the TabContents'
+ // HWND's DC to the capture bitmap produces blankness.
+ capture_window_->ShowWindow(SW_SHOWNOACTIVATE);
+ SetLayeredWindowAttributes(
+ capture_window_->GetHWND(), RGB(0xFF, 0xFF, 0xFF), 0xFF, LWA_ALPHA);
+
+ ReplaceHWND(initial_hwnd);
+}
diff --git a/chrome/browser/views/tabs/hwnd_photobooth.h b/chrome/browser/views/tabs/hwnd_photobooth.h
new file mode 100644
index 0000000..4de1d14
--- /dev/null
+++ b/chrome/browser/views/tabs/hwnd_photobooth.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef CHROME_BROWSER_VIEWS_TABS_HWND_PHOTOBOOTH_H__
+#define CHROME_BROWSER_VIEWS_TABS_HWND_PHOTOBOOTH_H__
+
+#include "base/basictypes.h"
+#include "base/gfx/rect.h"
+
+class ChromeCanvas;
+namespace ChromeViews {
+class HWNDViewContainer;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HWNDPhotobooth
+//
+// An object that a HWND "steps into" to have its picture taken. This is used
+// to generate a full size screen shot of the contents of a HWND including
+// any child windows.
+//
+// Implementation note: This causes the HWND to be re-parented to a mostly
+// off-screen layered window.
+//
+class HWNDPhotobooth {
+ public:
+ // Creates the photo booth. Constructs a nearly off-screen window, parents
+ // the HWND, then shows it. The caller is responsible for destroying this
+ // window, since the photo-booth will detach it before it is destroyed.
+ // |canvas| is a canvas to paint the contents into, and dest_bounds is the
+ // target area in |canvas| to which painted contents will be clipped.
+ explicit HWNDPhotobooth(HWND initial_hwnd);
+
+ // Destroys the photo booth window.
+ virtual ~HWNDPhotobooth();
+
+ // Replaces the HWND in the photo booth with the specified one. The caller is
+ // responsible for destroying this HWND since it will be detached from the
+ // capture window before the capture window is destroyed.
+ void ReplaceHWND(HWND new_hwnd);
+
+ // Paints the current display image of the window into |canvas|, clipped to
+ // |target_bounds|.
+ void PaintScreenshotIntoCanvas(ChromeCanvas* canvas,
+ const gfx::Rect& target_bounds);
+
+ private:
+ // Creates a mostly off-screen window to contain the HWND to be captured.
+ void CreateCaptureWindow(HWND initial_hwnd);
+
+ // The nearly off-screen photo-booth layered window used to hold the HWND.
+ ChromeViews::HWNDViewContainer* capture_window_;
+
+ // The current HWND being captured.
+ HWND current_hwnd_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(HWNDPhotobooth);
+};
+
+#endif // #ifndef CHROME_BROWSER_VIEWS_TABS_HWND_PHOTOBOOTH_H__
+
diff --git a/chrome/browser/views/tabs/tab.cc b/chrome/browser/views/tabs/tab.cc
new file mode 100644
index 0000000..bf5dc89
--- /dev/null
+++ b/chrome/browser/views/tabs/tab.cc
@@ -0,0 +1,241 @@
+// Copyright (c) 2006-2008 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.h"
+
+#include "base/gfx/size.h"
+#include "chrome/views/view_container.h"
+#include "chrome/common/gfx/chrome_canvas.h"
+#include "chrome/common/gfx/path.h"
+#include "chrome/common/resource_bundle.h"
+#include "chrome/views/chrome_menu.h"
+#include "chrome/views/tooltip_manager.h"
+#include "generated_resources.h"
+
+const std::string Tab::kTabClassName = "browser/tabs/Tab";
+
+static const SkScalar kTabCapWidth = 15;
+static const SkScalar kTabTopCurveWidth = 4;
+static const SkScalar kTabBottomCurveWidth = 3;
+
+class TabContextMenuController : public ChromeViews::MenuDelegate {
+ public:
+ explicit TabContextMenuController(Tab* tab)
+ : tab_(tab),
+ last_command_(TabStripModel::CommandFirst) {
+ menu_.reset(new ChromeViews::MenuItemView(this));
+ menu_->AppendMenuItemWithLabel(TabStripModel::CommandNewTab,
+ l10n_util::GetString(IDS_TAB_CXMENU_NEWTAB));
+ menu_->AppendSeparator();
+ menu_->AppendMenuItemWithLabel(TabStripModel::CommandReload,
+ l10n_util::GetString(IDS_TAB_CXMENU_RELOAD));
+ menu_->AppendMenuItemWithLabel(
+ TabStripModel::CommandDuplicate,
+ l10n_util::GetString(IDS_TAB_CXMENU_DUPLICATE));
+ menu_->AppendSeparator();
+ menu_->AppendMenuItemWithLabel(
+ TabStripModel::CommandCloseTab,
+ l10n_util::GetString(IDS_TAB_CXMENU_CLOSETAB));
+ menu_->AppendMenuItemWithLabel(
+ TabStripModel::CommandCloseOtherTabs,
+ l10n_util::GetString(IDS_TAB_CXMENU_CLOSEOTHERTABS));
+ menu_->AppendMenuItemWithLabel(
+ TabStripModel::CommandCloseTabsToRight,
+ l10n_util::GetString(IDS_TAB_CXMENU_CLOSETABSTORIGHT));
+ menu_->AppendMenuItemWithLabel(
+ TabStripModel::CommandCloseTabsOpenedBy,
+ l10n_util::GetString(IDS_TAB_CXMENU_CLOSETABSOPENEDBY));
+ }
+ virtual ~TabContextMenuController() {
+ tab_->delegate()->StopAllHighlighting();
+ }
+
+ void RunMenuAt(int x, int y) {
+ menu_->RunMenuAt(tab_->GetViewContainer()->GetHWND(),
+ gfx::Rect(x, y, 0, 0), ChromeViews::MenuItemView::TOPLEFT,
+ false);
+ }
+
+ private:
+ // ChromeViews::MenuDelegate implementation:
+ virtual bool IsCommandEnabled(int id) const {
+ // The MenuItemView used to contain the contents of the Context Menu itself
+ // has a command id of 0, and it will check to see if it's enabled for
+ // some reason during its construction. The TabStripModel can't handle
+ // command indices it doesn't know about, so we need to filter this out
+ // here.
+ if (id == 0)
+ return false;
+ return tab_->delegate()->IsCommandEnabledForTab(
+ static_cast<TabStripModel::ContextMenuCommand>(id),
+ tab_);
+ }
+
+ virtual void ExecuteCommand(int id) {
+ tab_->delegate()->ExecuteCommandForTab(
+ static_cast<TabStripModel::ContextMenuCommand>(id),
+ tab_);
+ }
+
+ virtual void SelectionChanged(ChromeViews::MenuItemView* menu) {
+ TabStripModel::ContextMenuCommand command =
+ static_cast<TabStripModel::ContextMenuCommand>(menu->GetCommand());
+ tab_->delegate()->StopHighlightTabsForCommand(last_command_, tab_);
+ last_command_ = command;
+ tab_->delegate()->StartHighlightTabsForCommand(command, tab_);
+ }
+
+ private:
+ // The context menu.
+ scoped_ptr<ChromeViews::MenuItemView> menu_;
+
+ // The Tab the context menu was brought up for.
+ Tab* tab_;
+
+ // The last command that was selected, so that we can start/stop highlighting
+ // appropriately as the user moves through the menu.
+ TabStripModel::ContextMenuCommand last_command_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TabContextMenuController);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Tab, public:
+
+Tab::Tab(TabDelegate* delegate)
+ : TabRenderer(),
+ delegate_(delegate),
+ closing_(false) {
+ close_button()->SetListener(this, 0);
+ close_button()->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_CLOSE));
+ close_button()->SetAnimationDuration(0);
+ SetContextMenuController(this);
+}
+
+Tab::~Tab() {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Tab, TabRenderer overrides:
+
+bool Tab::IsSelected() const {
+ return delegate_->IsTabSelected(this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Tab, ChromeViews::View overrides:
+
+bool Tab::HitTest(const CPoint &l) const {
+ gfx::Path path;
+ MakePathForTab(&path);
+ ScopedHRGN rgn(path.CreateHRGN());
+ return !!PtInRegion(rgn, l.x, l.y);
+}
+
+bool Tab::OnMousePressed(const ChromeViews::MouseEvent& event) {
+ if (event.IsOnlyLeftMouseButton()) {
+ // Store whether or not we were selected just now... we only want to be
+ // able to drag foreground tabs, so we don't start dragging the tab if
+ // it was in the background.
+ bool just_selected = !IsSelected();
+ if (just_selected)
+ delegate_->SelectTab(this);
+ delegate_->MaybeStartDrag(this, event);
+ }
+ return true;
+}
+
+bool Tab::OnMouseDragged(const ChromeViews::MouseEvent& event) {
+ delegate_->ContinueDrag(event);
+ return true;
+}
+
+void Tab::OnMouseReleased(const ChromeViews::MouseEvent& event,
+ bool canceled) {
+ // Notify the drag helper that we're done with any potential drag operations.
+ // Clean up the drag helper, which is re-created on the next mouse press.
+ delegate_->EndDrag(canceled);
+ if (event.IsMiddleMouseButton())
+ delegate_->CloseTab(this);
+}
+
+bool Tab::GetTooltipText(int x, int y, std::wstring* tooltip) {
+ std::wstring title = GetTitle();
+ if (!title.empty()) {
+ // Only show the tooltip if the title is truncated.
+ ChromeFont font;
+ if (font.GetStringWidth(title) > title_bounds().width()) {
+ *tooltip = title;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Tab::GetTooltipTextOrigin(int x, int y, CPoint* origin) {
+ ChromeFont font;
+ origin->x = title_bounds().x() + 10;
+ origin->y = -ChromeViews::TooltipManager::GetTooltipHeight() - 4;
+ return true;
+}
+
+bool Tab::GetAccessibleRole(VARIANT* role) {
+ DCHECK(role);
+
+ role->vt = VT_I4;
+ role->lVal = ROLE_SYSTEM_PAGETAB;
+ return true;
+}
+
+bool Tab::GetAccessibleName(std::wstring* name) {
+ *name = GetTitle();
+ return !name->empty();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Tab, ChromeViews::ContextMenuController implementation:
+
+void Tab::ShowContextMenu(ChromeViews::View* source, int x, int y,
+ bool is_mouse_gesture) {
+ TabContextMenuController controller(this);
+ controller.RunMenuAt(x, y);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// ChromeViews::BaseButton::ButtonListener implementation:
+
+void Tab::ButtonPressed(ChromeViews::BaseButton* sender) {
+ if (sender == close_button())
+ delegate_->CloseTab(this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Tab, private:
+
+void Tab::MakePathForTab(gfx::Path* path) const {
+ DCHECK(path);
+
+ SkScalar h = SkIntToScalar(GetHeight());
+ SkScalar w = SkIntToScalar(GetWidth());
+
+ path->moveTo(0, h);
+
+ // Left end cap.
+ path->lineTo(kTabBottomCurveWidth, h - kTabBottomCurveWidth);
+ path->lineTo(kTabCapWidth - kTabTopCurveWidth, kTabTopCurveWidth);
+ path->lineTo(kTabCapWidth, 0);
+
+ // Connect to the right cap.
+ path->lineTo(w - kTabCapWidth, 0);
+
+ // Right end cap.
+ path->lineTo(w - kTabCapWidth - kTabTopCurveWidth, kTabTopCurveWidth);
+ path->lineTo(w - kTabBottomCurveWidth, h - kTabBottomCurveWidth);
+ path->lineTo(w, h);
+
+ // Close out the path.
+ path->lineTo(0, h);
+ path->close();
+}
diff --git a/chrome/browser/views/tabs/tab.h b/chrome/browser/views/tabs/tab.h
new file mode 100644
index 0000000..753e1a9
--- /dev/null
+++ b/chrome/browser/views/tabs/tab.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef CHROME_BROWSER_VIEWS_TABS_TAB_H_
+#define CHROME_BROWSER_VIEWS_TABS_TAB_H_
+
+#include "chrome/browser/tabs/tab_strip_model.h"
+#include "chrome/browser/views/tabs/tab_renderer.h"
+#include "chrome/views/base_button.h"
+
+namespace gfx {
+class Path;
+class Point;
+}
+class TabContents;
+class Profile;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Tab
+//
+// A subclass of TabRenderer that represents an individual Tab in a TabStrip.
+//
+///////////////////////////////////////////////////////////////////////////////
+class Tab : public TabRenderer,
+ public ChromeViews::ContextMenuController,
+ public ChromeViews::BaseButton::ButtonListener {
+ public:
+ static const std::string kTabClassName;
+
+ // An interface implemented by an object that can help this Tab complete
+ // various actions. The index parameter is the index of this Tab in the
+ // TabRenderer::Model.
+ class TabDelegate {
+ public:
+ // Returns true if the specified Tab is selected.
+ virtual bool IsTabSelected(const Tab* tab) const = 0;
+
+ // Selects the specified Tab.
+ virtual void SelectTab(Tab* tab) = 0;
+
+ // Closes the specified Tab.
+ virtual void CloseTab(Tab* tab) = 0;
+
+ // Returns true if the specified command is enabled for the specified Tab.
+ virtual bool IsCommandEnabledForTab(
+ TabStripModel::ContextMenuCommand command_id, const Tab* tab) const = 0;
+
+ // Executes the specified command for the specified Tab.
+ virtual void ExecuteCommandForTab(
+ TabStripModel::ContextMenuCommand command_id, Tab* tab) = 0;
+
+ // Starts/Stops highlighting the tabs that will be affected by the
+ // specified command for the specified Tab.
+ virtual void StartHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id, Tab* tab) = 0;
+ virtual void StopHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id, Tab* tab) = 0;
+ virtual void StopAllHighlighting() = 0;
+
+ // Potentially starts a drag for the specified Tab.
+ virtual void MaybeStartDrag(Tab* tab,
+ const ChromeViews::MouseEvent& event) = 0;
+
+ // Continues dragging a Tab.
+ virtual void ContinueDrag(const ChromeViews::MouseEvent& event) = 0;
+
+ // Ends dragging a Tab. |canceled| is true if the drag was aborted in a way
+ // other than the user releasing the mouse.
+ virtual void EndDrag(bool canceled) = 0;
+ };
+
+ explicit Tab(TabDelegate* delegate);
+ virtual ~Tab();
+
+ // Access the delegate.
+ TabDelegate* delegate() const { return delegate_; }
+
+ // Used to set/check whether this Tab is being animated closed.
+ void set_closing(bool closing) { closing_ = closing; }
+ bool closing() const { return closing_; }
+
+ // TabRenderer overrides:
+ virtual bool IsSelected() const;
+
+ // ChromeViews::View overrides:
+ virtual bool HitTest(const CPoint &l) const;
+ private:
+ // ChromeViews::View overrides:
+ virtual bool OnMousePressed(const ChromeViews::MouseEvent& event);
+ virtual bool OnMouseDragged(const ChromeViews::MouseEvent& event);
+ virtual void OnMouseReleased(const ChromeViews::MouseEvent& event,
+ bool canceled);
+ virtual bool GetTooltipText(int x, int y, std::wstring* tooltip);
+ virtual bool GetTooltipTextOrigin(int x, int y, CPoint* origin);
+ virtual std::string GetClassName() const { return kTabClassName; }
+ virtual bool GetAccessibleRole(VARIANT* role);
+ virtual bool GetAccessibleName(std::wstring* name);
+
+ // ChromeViews::ContextMenuController overrides:
+ virtual void ShowContextMenu(ChromeViews::View* source,
+ int x,
+ int y,
+ bool is_mouse_gesture);
+
+ // ChromeViews::BaseButton::ButtonListener overrides:
+ virtual void ButtonPressed(ChromeViews::BaseButton* sender);
+
+ // Creates a path that contains the clickable region of the tab's visual
+ // representation. Used by GetViewForPoint for hit-testing.
+ void MakePathForTab(gfx::Path* path) const;
+
+ // An instance of a delegate object that can perform various actions based on
+ // user gestures.
+ TabDelegate* delegate_;
+
+ // True if the tab is being animated closed.
+ bool closing_;
+
+ DISALLOW_COPY_AND_ASSIGN(Tab);
+};
+
+#endif // CHROME_BROWSER_VIEWS_TABS_TAB_H_
+
diff --git a/chrome/browser/views/tabs/tab_dragging_test.cc b/chrome/browser/views/tabs/tab_dragging_test.cc
new file mode 100644
index 0000000..631a0f0
--- /dev/null
+++ b/chrome/browser/views/tabs/tab_dragging_test.cc
@@ -0,0 +1,507 @@
+// Copyright (c) 2006-2008 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 "base/command_line.h"
+#include "base/file_util.h"
+#include "base/time.h"
+#include "chrome/app/chrome_dll_resource.h"
+#include "chrome/browser/view_ids.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/automation/tab_proxy.h"
+#include "chrome/test/automation/browser_proxy.h"
+#include "chrome/test/automation/window_proxy.h"
+#include "chrome/test/ui/ui_test.h"
+#include "chrome/views/event.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_util.h"
+
+
+class TabDraggingTest : public UITest {
+protected:
+ TabDraggingTest() {
+ show_window_ = true;
+ }
+};
+
+// Automated UI test to open three tabs in a new window, and drag Tab_1 into
+// the position of Tab_2.
+TEST_F(TabDraggingTest, Tab1Tab2) {
+ scoped_ptr<BrowserProxy> browser(automation()->GetBrowserWindow(0));
+ ASSERT_TRUE(browser.get());
+ scoped_ptr<WindowProxy> window(
+ automation()->GetWindowForBrowser(browser.get()));
+ ASSERT_TRUE(window.get());
+
+ // Get initial tab count.
+ int initial_tab_count = 0;
+ ASSERT_TRUE(browser->GetTabCount(&initial_tab_count));
+ ASSERT_TRUE(1 == initial_tab_count);
+
+ // Get Tab_1 which comes with the browser window.
+ scoped_ptr<TabProxy> tab1(browser->GetTab(0));
+ ASSERT_TRUE(tab1.get());
+ GURL tab1_url;
+ ASSERT_TRUE(tab1->GetCurrentURL(&tab1_url));
+
+ // Add Tab_2.
+ GURL tab2_url("about:");
+ ASSERT_TRUE(browser->AppendTab(tab2_url));
+ scoped_ptr<TabProxy> tab2(browser->GetTab(1));
+ ASSERT_TRUE(tab2.get());
+
+ // Add Tab_3.
+ GURL tab3_url("about:plugins");
+ ASSERT_TRUE(browser->AppendTab(tab3_url));
+ scoped_ptr<TabProxy> tab3(browser->GetTab(2));
+ ASSERT_TRUE(tab3.get());
+
+ // Make sure 3 tabs are open
+ int final_tab_count = 0;
+ ASSERT_TRUE(browser->WaitForTabCountToChange(initial_tab_count,
+ &final_tab_count,
+ 10000));
+ ASSERT_TRUE(final_tab_count == initial_tab_count + 2);
+
+ // Get bounds for the tabs.
+ gfx::Rect bounds1;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_0, &bounds1, false));
+ EXPECT_LT(0, bounds1.x());
+ EXPECT_LT(0, bounds1.width());
+ EXPECT_LT(0, bounds1.height());
+
+ gfx::Rect bounds2;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_1, &bounds2, false));
+ EXPECT_LT(0, bounds2.width());
+ EXPECT_LT(0, bounds2.height());
+ EXPECT_LT(bounds1.x(), bounds2.x());
+ EXPECT_EQ(bounds2.y(), bounds1.y());
+
+ gfx::Rect bounds3;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_2, &bounds3, false));
+ EXPECT_LT(0, bounds3.width());
+ EXPECT_LT(0, bounds3.height());
+ EXPECT_LT(bounds2.x(), bounds3.x());
+ EXPECT_EQ(bounds3.y(), bounds2.y());
+
+ // Get url Bar bounds.
+ gfx::Rect urlbar_bounds;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_LOCATION_BAR, &urlbar_bounds,
+ false));
+ EXPECT_LT(0, urlbar_bounds.x());
+ EXPECT_LT(0, urlbar_bounds.y());
+ EXPECT_LT(0, urlbar_bounds.width());
+ EXPECT_LT(0, urlbar_bounds.height());
+
+ // TEST: Move Tab_1 to the position of Tab_2
+ // ____________ ____________ ____________
+ // / \ / \ / \
+ // | Tab_1 | Tab_2 | Tab_3 |
+ // ---- ---- ---- ---- ---- ---- ---- ---- ----
+ // x---- ---->
+ // ____________
+ // / X \
+ // | Tab_1 |
+ // ---- ---- ----
+
+ POINT start;
+ POINT end;
+ start.x = bounds1.x() + bounds1.width()/2;
+ start.y = bounds1.y() + bounds1.height()/2;
+ end.x = start.x + 2*bounds1.width()/3;
+ end.y = start.y;
+ ASSERT_TRUE(browser->SimulateDrag(start, end,
+ ChromeViews::Event::EF_LEFT_BUTTON_DOWN,
+ false));
+
+ // Now check for expected results.
+ tab1.reset(browser->GetTab(0));
+ ASSERT_TRUE(tab1.get());
+ GURL tab1_new_url;
+ ASSERT_TRUE(tab1->GetCurrentURL(&tab1_new_url));
+
+ tab2.reset(browser->GetTab(1));
+ ASSERT_TRUE(tab2.get());
+ GURL tab2_new_url;
+ ASSERT_TRUE(tab2->GetCurrentURL(&tab2_new_url));
+
+ EXPECT_EQ(tab1_url.spec(), tab2_new_url.spec());
+ EXPECT_EQ(tab2_url.spec(), tab1_new_url.spec());
+}
+
+// Drag Tab_1 into the position of Tab_3.
+TEST_F(TabDraggingTest, Tab1Tab3) {
+ scoped_ptr<BrowserProxy> browser(automation()->GetBrowserWindow(0));
+ ASSERT_TRUE(browser.get());
+ scoped_ptr<WindowProxy> window(
+ automation()->GetWindowForBrowser(browser.get()));
+ ASSERT_TRUE(window.get());
+
+ // Get initial tab count.
+ int initial_tab_count = 0;
+ ASSERT_TRUE(browser->GetTabCount(&initial_tab_count));
+ ASSERT_TRUE(1 == initial_tab_count);
+
+ // Get Tab_1 which comes with the browser window.
+ scoped_ptr<TabProxy> tab1(browser->GetTab(0));
+ ASSERT_TRUE(tab1.get());
+ GURL tab1_url;
+ ASSERT_TRUE(tab1->GetCurrentURL(&tab1_url));
+
+ // Add Tab_2.
+ GURL tab2_url("about:");
+ ASSERT_TRUE(browser->AppendTab(tab2_url));
+ scoped_ptr<TabProxy> tab2(browser->GetTab(1));
+ ASSERT_TRUE(tab2.get());
+
+ // Add Tab_3.
+ GURL tab3_url("about:plugins");
+ ASSERT_TRUE(browser->AppendTab(tab3_url));
+ scoped_ptr<TabProxy> tab3(browser->GetTab(2));
+ ASSERT_TRUE(tab3.get());
+
+ // Make sure 3 tabs are open
+ int final_tab_count = 0;
+ ASSERT_TRUE(browser->WaitForTabCountToChange(initial_tab_count,
+ &final_tab_count,
+ 10000));
+ ASSERT_TRUE(final_tab_count == initial_tab_count + 2);
+
+ // Get bounds for the tabs.
+ gfx::Rect bounds1;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_0, &bounds1, false));
+ EXPECT_LT(0, bounds1.x());
+ EXPECT_LT(0, bounds1.width());
+ EXPECT_LT(0, bounds1.height());
+
+ gfx::Rect bounds2;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_1, &bounds2, false));
+ EXPECT_LT(0, bounds2.width());
+ EXPECT_LT(0, bounds2.height());
+ EXPECT_LT(bounds1.x(), bounds2.x());
+ EXPECT_EQ(bounds2.y(), bounds1.y());
+
+ gfx::Rect bounds3;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_2, &bounds3, false));
+ EXPECT_LT(0, bounds3.width());
+ EXPECT_LT(0, bounds3.height());
+ EXPECT_LT(bounds2.x(), bounds3.x());
+ EXPECT_EQ(bounds3.y(), bounds2.y());
+
+ // Get url Bar bounds.
+ gfx::Rect urlbar_bounds;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_LOCATION_BAR, &urlbar_bounds,
+ false));
+ EXPECT_LT(0, urlbar_bounds.x());
+ EXPECT_LT(0, urlbar_bounds.y());
+ EXPECT_LT(0, urlbar_bounds.width());
+ EXPECT_LT(0, urlbar_bounds.height());
+
+ // TEST: Move Tab_1 to the middle position of Tab_3
+ // ____________ ____________ ____________
+ // / \ / \ / \
+ // | Tab_1 | Tab_2 | Tab_3 |
+ // ---- ---- ---- ---- ---- ---- ---- ---- ----
+ // x---- ---- ---- ---- ---- ---->
+ // ____________
+ // / X \
+ // | Tab_1 |
+ // ---- ---- ----
+
+ POINT start;
+ POINT end;
+ start.x = bounds1.x() + bounds1.width()/2;
+ start.y = bounds1.y() + bounds1.height()/2;
+ end.x = start.x + bounds1.width()/2 + bounds2.width() + bounds3.width()/2;
+ end.y = start.y;
+ ASSERT_TRUE(browser->SimulateDrag(start, end,
+ ChromeViews::Event::EF_LEFT_BUTTON_DOWN,
+ false));
+
+ // Now check for expected results.
+ tab1.reset(browser->GetTab(0));
+ ASSERT_TRUE(tab1.get());
+ GURL tab1_new_url;
+ ASSERT_TRUE(tab1->GetCurrentURL(&tab1_new_url));
+
+ tab2.reset(browser->GetTab(1));
+ ASSERT_TRUE(tab2.get());
+ GURL tab2_new_url;
+ ASSERT_TRUE(tab2->GetCurrentURL(&tab2_new_url));
+
+ tab3.reset(browser->GetTab(2));
+ ASSERT_TRUE(tab3.get());
+ GURL tab3_new_url;
+ ASSERT_TRUE(tab3->GetCurrentURL(&tab3_new_url));
+
+ EXPECT_EQ(tab1_new_url.spec(), tab2_url.spec());
+ EXPECT_EQ(tab2_new_url.spec(), tab3_url.spec());
+ EXPECT_EQ(tab3_new_url.spec(), tab1_url.spec());
+}
+
+// Drag Tab_1 into the position of Tab_3, and press ESCAPE before releasing the
+// left mouse button.
+TEST_F(TabDraggingTest, Tab1Tab3Escape) {
+ scoped_ptr<BrowserProxy> browser(automation()->GetBrowserWindow(0));
+ ASSERT_TRUE(browser.get());
+ scoped_ptr<WindowProxy> window(
+ automation()->GetWindowForBrowser(browser.get()));
+ ASSERT_TRUE(window.get());
+
+ // Get initial tab count.
+ int initial_tab_count = 0;
+ ASSERT_TRUE(browser->GetTabCount(&initial_tab_count));
+ ASSERT_TRUE(1 == initial_tab_count);
+
+ // Get Tab_1 which comes with the browser window.
+ scoped_ptr<TabProxy> tab1(browser->GetTab(0));
+ ASSERT_TRUE(tab1.get());
+ GURL tab1_url;
+ ASSERT_TRUE(tab1->GetCurrentURL(&tab1_url));
+
+ // Add Tab_2.
+ GURL tab2_url("about:");
+ ASSERT_TRUE(browser->AppendTab(tab2_url));
+ scoped_ptr<TabProxy> tab2(browser->GetTab(1));
+ ASSERT_TRUE(tab2.get());
+
+ // Add Tab_3.
+ GURL tab3_url("about:plugins");
+ ASSERT_TRUE(browser->AppendTab(tab3_url));
+ scoped_ptr<TabProxy> tab3(browser->GetTab(2));
+ ASSERT_TRUE(tab3.get());
+
+ // Make sure 3 tabs are open
+ int final_tab_count = 0;
+ ASSERT_TRUE(browser->WaitForTabCountToChange(initial_tab_count,
+ &final_tab_count,
+ 10000));
+ ASSERT_TRUE(final_tab_count == initial_tab_count + 2);
+
+ // Get bounds for the tabs.
+ gfx::Rect bounds1;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_0, &bounds1, false));
+ EXPECT_LT(0, bounds1.x());
+ EXPECT_LT(0, bounds1.width());
+ EXPECT_LT(0, bounds1.height());
+
+ gfx::Rect bounds2;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_1, &bounds2, false));
+ EXPECT_LT(0, bounds2.width());
+ EXPECT_LT(0, bounds2.height());
+ EXPECT_LT(bounds1.x(), bounds2.x());
+ EXPECT_EQ(bounds2.y(), bounds1.y());
+
+ gfx::Rect bounds3;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_2, &bounds3, false));
+ EXPECT_LT(0, bounds3.width());
+ EXPECT_LT(0, bounds3.height());
+ EXPECT_LT(bounds2.x(), bounds3.x());
+ EXPECT_EQ(bounds3.y(), bounds2.y());
+
+ // Get url Bar bounds.
+ gfx::Rect urlbar_bounds;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_LOCATION_BAR, &urlbar_bounds,
+ false));
+ EXPECT_LT(0, urlbar_bounds.x());
+ EXPECT_LT(0, urlbar_bounds.y());
+ EXPECT_LT(0, urlbar_bounds.width());
+ EXPECT_LT(0, urlbar_bounds.height());
+
+ // TEST: Move Tab_1 to the middle position of Tab_3
+ // ____________ ____________ ____________
+ // / \ / \ / \
+ // | Tab_1 | Tab_2 | Tab_3 |
+ // ---- ---- ---- ---- ---- ---- ---- ---- ----
+ // x---- ---- ---- ---- ---- ----> + ESCAPE
+ // ____________
+ // / X \
+ // | Tab_1 |
+ // ---- ---- ----
+
+ POINT start;
+ POINT end;
+ start.x = bounds1.x() + bounds1.width()/2;
+ start.y = bounds1.y() + bounds1.height()/2;
+ end.x = start.x + bounds1.width()/2 + bounds2.width() + bounds3.width()/2;
+ end.y = start.y;
+
+ // Simulate drag with 'true' as the last parameter. This will interrupt
+ // in-flight with Escape.
+ ASSERT_TRUE(browser->SimulateDrag(start, end,
+ ChromeViews::Event::EF_LEFT_BUTTON_DOWN,
+ true));
+
+ // Now check for expected results.
+ tab1.reset(browser->GetTab(0));
+ ASSERT_TRUE(tab1.get());
+ GURL tab1_new_url;
+ ASSERT_TRUE(tab1->GetCurrentURL(&tab1_new_url));
+
+ tab2.reset(browser->GetTab(1));
+ ASSERT_TRUE(tab2.get());
+ GURL tab2_new_url;
+ ASSERT_TRUE(tab2->GetCurrentURL(&tab2_new_url));
+
+ tab3.reset(browser->GetTab(2));
+ ASSERT_TRUE(tab3.get());
+ GURL tab3_new_url;
+ ASSERT_TRUE(tab3->GetCurrentURL(&tab3_new_url));
+
+ // The tabs should be in their original positions.
+ EXPECT_EQ(tab1_new_url.spec(), tab1_url.spec());
+ EXPECT_EQ(tab2_new_url.spec(), tab2_url.spec());
+ EXPECT_EQ(tab3_new_url.spec(), tab3_url.spec());
+}
+
+// Drag Tab_2 out of the Tab strip. A new window should open with this tab.
+TEST_F(TabDraggingTest, Tab2OutOfTabStrip) {
+ scoped_ptr<BrowserProxy> browser(automation()->GetBrowserWindow(0));
+ ASSERT_TRUE(browser.get());
+ scoped_ptr<WindowProxy> window(
+ automation()->GetWindowForBrowser(browser.get()));
+ ASSERT_TRUE(window.get());
+
+ // Get initial tab count.
+ int initial_tab_count = 0;
+ ASSERT_TRUE(browser->GetTabCount(&initial_tab_count));
+ ASSERT_TRUE(1 == initial_tab_count);
+
+ // Get Tab_1 which comes with the browser window.
+ scoped_ptr<TabProxy> tab1(browser->GetTab(0));
+ ASSERT_TRUE(tab1.get());
+ GURL tab1_url;
+ ASSERT_TRUE(tab1->GetCurrentURL(&tab1_url));
+
+ // Add Tab_2.
+ GURL tab2_url("about:version");
+ ASSERT_TRUE(browser->AppendTab(tab2_url));
+ scoped_ptr<TabProxy> tab2(browser->GetTab(1));
+ ASSERT_TRUE(tab2.get());
+
+ // Add Tab_3.
+ GURL tab3_url("about:plugins");
+ ASSERT_TRUE(browser->AppendTab(tab3_url));
+ scoped_ptr<TabProxy> tab3(browser->GetTab(2));
+ ASSERT_TRUE(tab3.get());
+
+ // Make sure 3 tabs are opened.
+ int final_tab_count = 0;
+ ASSERT_TRUE(browser->WaitForTabCountToChange(initial_tab_count,
+ &final_tab_count,
+ 10000));
+ ASSERT_TRUE(final_tab_count == initial_tab_count + 2);
+
+ // Make sure all the tab URL specs are different.
+ ASSERT_TRUE(tab1_url != tab2_url);
+ ASSERT_TRUE(tab1_url != tab3_url);
+ ASSERT_TRUE(tab2_url != tab3_url);
+
+ // Get bounds for the tabs.
+ gfx::Rect bounds1;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_0, &bounds1, false));
+ EXPECT_LT(0, bounds1.x());
+ EXPECT_LT(0, bounds1.width());
+ EXPECT_LT(0, bounds1.height());
+
+ gfx::Rect bounds2;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_1, &bounds2, false));
+ EXPECT_LT(0, bounds2.width());
+ EXPECT_LT(0, bounds2.height());
+ EXPECT_LT(bounds1.x(), bounds2.x());
+ EXPECT_EQ(bounds2.y(), bounds1.y());
+
+ gfx::Rect bounds3;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_2, &bounds3, false));
+ EXPECT_LT(0, bounds3.width());
+ EXPECT_LT(0, bounds3.height());
+ EXPECT_LT(bounds2.x(), bounds3.x());
+ EXPECT_EQ(bounds3.y(), bounds2.y());
+
+ // Get url Bar bounds.
+ gfx::Rect urlbar_bounds;
+ ASSERT_TRUE(window->GetViewBounds(VIEW_ID_LOCATION_BAR, &urlbar_bounds,
+ false));
+ EXPECT_LT(0, urlbar_bounds.x());
+ EXPECT_LT(0, urlbar_bounds.y());
+ EXPECT_LT(0, urlbar_bounds.width());
+ EXPECT_LT(0, urlbar_bounds.height());
+
+ // TEST: Move Tab_2 down, out of the tab strip.
+ // This should result in the following:
+ // 1- Tab_3 shift left in place of Tab_2 in Window 1
+ // 2- Tab_1 to remain in its place
+ // 3- Tab_2 openes in a new window
+ //
+ // ____________ ____________ ____________
+ // / \ / \ / \
+ // | Tab_1 | Tab_2 | Tab_3 |
+ // ---- ---- ---- ---- ---- ---- ---- ---- ----
+ // x
+ // |
+ // | (Drag this below, out of tab strip)
+ // V
+ // ____________
+ // / X \
+ // | Tab_2 | (New Window)
+ // ---- ---- ---- ---- ---- ---- ----
+
+ POINT start;
+ POINT end;
+ start.x = bounds2.x() + bounds2.width()/2;
+ start.y = bounds2.y() + bounds2.height()/2;
+ end.x = start.x;
+ end.y = start.y + 3*urlbar_bounds.height();
+
+ // Simulate tab drag.
+ ASSERT_TRUE(browser->SimulateDrag(start, end,
+ ChromeViews::Event::EF_LEFT_BUTTON_DOWN,
+ false));
+
+ // Now, first make sure that the old window has only two tabs remaining.
+ int new_tab_count = 0;
+ ASSERT_TRUE(browser->GetTabCount(&new_tab_count));
+ ASSERT_EQ(2, new_tab_count);
+
+ // Get the two tabs - they are called Tab_1 and Tab_2 in the old window.
+ tab1.reset(browser->GetTab(0));
+ ASSERT_TRUE(tab1.get());
+ GURL tab1_new_url;
+ ASSERT_TRUE(tab1->GetCurrentURL(&tab1_new_url));
+
+ tab2.reset(browser->GetTab(1));
+ ASSERT_TRUE(tab2.get());
+ GURL tab2_new_url;
+ ASSERT_TRUE(tab2->GetCurrentURL(&tab2_new_url));
+
+ // Now check for proper shifting of tabs; i.e., Tab_3 in window 1 should
+ // shift left to the position of Tab_2; Tab_1 should stay where it was.
+ EXPECT_EQ(tab1_new_url.spec(), tab1_url.spec());
+ EXPECT_EQ(tab2_new_url.spec(), tab3_url.spec());
+
+ // Now check to make sure a new window has opened.
+ scoped_ptr<BrowserProxy> browser2(automation()->GetBrowserWindow(1));
+ ASSERT_TRUE(browser2.get());
+ scoped_ptr<WindowProxy> window2(
+ automation()->GetWindowForBrowser(browser2.get()));
+ ASSERT_TRUE(window2.get());
+
+ // Make sure that the new window has only one tab.
+ int tab_count_window_2 = 0;
+ ASSERT_TRUE(browser2->GetTabCount(&tab_count_window_2));
+ ASSERT_EQ(1, tab_count_window_2);
+
+ // Get Tab_1_2 which should be Tab_1 in Window 2.
+ scoped_ptr<TabProxy> tab1_2(browser2->GetTab(0));
+ ASSERT_TRUE(tab1_2.get());
+ GURL tab1_2_url;
+ ASSERT_TRUE(tab1_2->GetCurrentURL(&tab1_2_url));
+
+ // Tab_1_2 of Window 2 should essentially be Tab_2 of Window 1.
+ EXPECT_EQ(tab1_2_url.spec(), tab2_url.spec());
+ EXPECT_NE(tab1_2_url.spec(), tab1_url.spec());
+ EXPECT_NE(tab1_2_url.spec(), tab3_url.spec());
+}
+
diff --git a/chrome/browser/views/tabs/tab_renderer.cc b/chrome/browser/views/tabs/tab_renderer.cc
new file mode 100644
index 0000000..d7509ab
--- /dev/null
+++ b/chrome/browser/views/tabs/tab_renderer.cc
@@ -0,0 +1,691 @@
+// Copyright (c) 2006-2008 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 <limits>
+
+#include "chrome/browser/views/tabs/tab_renderer.h"
+
+#include "base/gfx/image_operations.h"
+#include "chrome/app/theme/theme_resources.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/tabs/tab_strip_model.h"
+#include "chrome/browser/tab_contents.h"
+#include "chrome/common/gfx/chrome_canvas.h"
+#include "chrome/common/gfx/chrome_font.h"
+#include "chrome/common/resource_bundle.h"
+#include "chrome/common/win_util.h"
+#include "generated_resources.h"
+
+static const int kLeftPadding = 16;
+static const int kTopPadding = 6;
+static const int kRightPadding = 15;
+static const int kBottomPadding = 5;
+static const int kFavIconTitleSpacing = 4;
+static const int kTitleCloseButtonSpacing = 5;
+static const int kStandardTitleWidth = 175;
+static const int kCloseButtonVertFuzz = 0;
+static const int kCloseButtonHorzFuzz = 5;
+static const int kFaviconSize = 16;
+static const int kSelectedTitleColor = SK_ColorBLACK;
+static const int kUnselectedTitleColor = SkColorSetRGB(64, 64, 64);
+
+// How long the hover state takes.
+static const int kHoverDurationMs = 90;
+
+// How long the pulse throb takes.
+static const int kPulseDurationMs = 200;
+
+// How opaque to make the hover state (out of 1).
+static const double kHoverOpacity = 0.33;
+static const double kHoverOpacityVista = 0.7;
+
+// TODO(beng): (Cleanup) This stuff should move onto the class.
+static ChromeFont title_font;
+static int title_font_height = 0;
+static SkBitmap* close_button_n = NULL;
+static SkBitmap* close_button_h = NULL;
+static SkBitmap* close_button_p = NULL;
+static int close_button_height = 0;
+static int close_button_width = 0;
+static SkBitmap* tab_active_l = NULL;
+static SkBitmap* tab_active_c = NULL;
+static SkBitmap* tab_active_r = NULL;
+static int tab_active_l_width = 0;
+static int tab_active_r_width = 0;
+static SkBitmap* tab_inactive_l = NULL;
+static SkBitmap* tab_inactive_c = NULL;
+static SkBitmap* tab_inactive_r = NULL;
+static SkBitmap* tab_inactive_otr_l = NULL;
+static SkBitmap* tab_inactive_otr_c = NULL;
+static SkBitmap* tab_inactive_otr_r = NULL;
+static SkBitmap* tab_hover_l = NULL;
+static SkBitmap* tab_hover_c = NULL;
+static SkBitmap* tab_hover_r = NULL;
+static int tab_inactive_l_width = 0;
+static int tab_inactive_r_width = 0;
+static SkBitmap* waiting_animation_frames = NULL;
+static SkBitmap* loading_animation_frames = NULL;
+static SkBitmap* crashed_fav_icon = NULL;
+static int loading_animation_frame_count = 0;
+static int waiting_animation_frame_count = 0;
+static int waiting_to_loading_frame_count_ratio = 0;
+static SkBitmap* download_icon = NULL;
+static int download_icon_width = 0;
+static int download_icon_height = 0;
+
+namespace {
+
+void InitResources() {
+ static bool initialized = false;
+ if (!initialized) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ title_font = rb.GetFont(ResourceBundle::BaseFont);
+ title_font_height = title_font.height();
+
+ close_button_n = rb.GetBitmapNamed(IDR_TAB_CLOSE);
+ close_button_h = rb.GetBitmapNamed(IDR_TAB_CLOSE_H);
+ close_button_p = rb.GetBitmapNamed(IDR_TAB_CLOSE_P);
+ close_button_width = close_button_n->width();
+ close_button_height = close_button_n->height();
+
+ tab_active_l = rb.GetBitmapNamed(IDR_TAB_ACTIVE_LEFT);
+ tab_active_c = rb.GetBitmapNamed(IDR_TAB_ACTIVE_CENTER);
+ tab_active_r = rb.GetBitmapNamed(IDR_TAB_ACTIVE_RIGHT);
+ tab_active_l_width = tab_active_l->width();
+ tab_active_r_width = tab_active_r->width();
+
+ if (win_util::ShouldUseVistaFrame()) {
+ tab_inactive_l = rb.GetBitmapNamed(IDR_TAB_INACTIVE_LEFT_V);
+ tab_inactive_c = rb.GetBitmapNamed(IDR_TAB_INACTIVE_CENTER_V);
+ tab_inactive_r = rb.GetBitmapNamed(IDR_TAB_INACTIVE_RIGHT_V);
+
+ // Our Vista frame doesn't change background color to show OTR,
+ // so we continue to use the existing background tabs.
+ tab_inactive_otr_l = rb.GetBitmapNamed(IDR_TAB_INACTIVE_LEFT_V);
+ tab_inactive_otr_c = rb.GetBitmapNamed(IDR_TAB_INACTIVE_CENTER_V);
+ tab_inactive_otr_r = rb.GetBitmapNamed(IDR_TAB_INACTIVE_RIGHT_V);
+ } else {
+ tab_inactive_l = rb.GetBitmapNamed(IDR_TAB_INACTIVE_LEFT);
+ tab_inactive_c = rb.GetBitmapNamed(IDR_TAB_INACTIVE_CENTER);
+ tab_inactive_r = rb.GetBitmapNamed(IDR_TAB_INACTIVE_RIGHT);
+
+ tab_inactive_otr_l = rb.GetBitmapNamed(IDR_TAB_INACTIVE_LEFT_OTR);
+ tab_inactive_otr_c = rb.GetBitmapNamed(IDR_TAB_INACTIVE_CENTER_OTR);
+ tab_inactive_otr_r = rb.GetBitmapNamed(IDR_TAB_INACTIVE_RIGHT_OTR);
+ }
+
+ tab_hover_l = rb.GetBitmapNamed(IDR_TAB_HOVER_LEFT);
+ tab_hover_c = rb.GetBitmapNamed(IDR_TAB_HOVER_CENTER);
+ tab_hover_r = rb.GetBitmapNamed(IDR_TAB_HOVER_RIGHT);
+
+ tab_inactive_l_width = tab_inactive_l->width();
+ tab_inactive_r_width = tab_inactive_r->width();
+
+ // The loading animation image is a strip of states. Each state must be
+ // square, so the height must divide the width evenly.
+ loading_animation_frames = rb.GetBitmapNamed(IDR_THROBBER);
+ DCHECK(loading_animation_frames);
+ DCHECK(loading_animation_frames->width() %
+ loading_animation_frames->height() == 0);
+ loading_animation_frame_count =
+ loading_animation_frames->width() / loading_animation_frames->height();
+
+ waiting_animation_frames = rb.GetBitmapNamed(IDR_THROBBER_WAITING);
+ DCHECK(waiting_animation_frames);
+ DCHECK(waiting_animation_frames->width() %
+ waiting_animation_frames->height() == 0);
+ waiting_animation_frame_count =
+ waiting_animation_frames->width() / waiting_animation_frames->height();
+
+ waiting_to_loading_frame_count_ratio =
+ waiting_animation_frame_count / loading_animation_frame_count;
+
+ crashed_fav_icon = rb.GetBitmapNamed(IDR_SAD_FAVICON);
+
+ download_icon = rb.GetBitmapNamed(IDR_DOWNLOAD_ICON);
+ download_icon_width = download_icon->width();
+ download_icon_height = download_icon->height();
+
+ initialized = true;
+ }
+}
+
+int GetContentHeight() {
+ // The height of the content of the Tab is the largest of the favicon,
+ // the title text and the close button graphic.
+ int content_height = std::max(kFaviconSize, title_font_height);
+ return std::max(content_height, close_button_height);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabCloseButton
+//
+// This is a Button subclass that causes middle clicks to be forwarded to the
+// parent View by explicitly not handling them in OnMousePressed.
+class TabCloseButton : public ChromeViews::Button {
+ public:
+ TabCloseButton() : Button() {}
+ virtual ~TabCloseButton() {}
+
+ virtual bool OnMousePressed(const ChromeViews::MouseEvent& event) {
+ return !event.IsOnlyMiddleMouseButton();
+ }
+
+ // We need to let the parent know about mouse state so that it
+ // can highlight itself appropriately. Note that Exit events
+ // fire before Enter events, so this works.
+ virtual void OnMouseEntered(const ChromeViews::MouseEvent& event) {
+ BaseButton::OnMouseEntered(event);
+ GetParent()->OnMouseEntered(event);
+ }
+
+ virtual void OnMouseExited(const ChromeViews::MouseEvent& event) {
+ BaseButton::OnMouseExited(event);
+ GetParent()->OnMouseExited(event);
+ }
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(TabCloseButton);
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// FaviconCrashAnimation
+//
+// A custom animation subclass to manage the favicon crash animation.
+class TabRenderer::FavIconCrashAnimation : public Animation,
+ public AnimationDelegate {
+ public:
+ explicit FavIconCrashAnimation(TabRenderer* target)
+ : Animation(1000, 25, this),
+ target_(target) {
+ }
+ virtual ~FavIconCrashAnimation() {}
+
+ // Animation overrides:
+ virtual void AnimateToState(double state) {
+ const double kHidingOffset = 27;
+
+ if (state < .5) {
+ target_->SetFavIconHidingOffset(
+ static_cast<int>(floor(kHidingOffset * 2.0 * state)));
+ } else {
+ target_->DisplayCrashedFavIcon();
+ target_->SetFavIconHidingOffset(
+ static_cast<int>(
+ floor(kHidingOffset - ((state - .5) * 2.0 * kHidingOffset))));
+ }
+ }
+
+ // AnimationDelegate overrides:
+ virtual void AnimationCanceled(const Animation* animation) {
+ target_->SetFavIconHidingOffset(0);
+ }
+
+ private:
+ TabRenderer* target_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(FavIconCrashAnimation);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// TabRenderer, public:
+
+TabRenderer::TabRenderer()
+ : animation_state_(ANIMATION_NONE),
+ animation_frame_(0),
+ showing_icon_(false),
+ showing_download_icon_(false),
+ showing_close_button_(false),
+ crash_animation_(NULL),
+ fav_icon_hiding_offset_(0),
+ should_display_crashed_favicon_(false) {
+ InitResources();
+
+ // Add the Close Button.
+ close_button_ = new TabCloseButton;
+ close_button_->SetImage(ChromeViews::Button::BS_NORMAL, close_button_n);
+ close_button_->SetImage(ChromeViews::Button::BS_HOT, close_button_h);
+ close_button_->SetImage(ChromeViews::Button::BS_PUSHED, close_button_p);
+ AddChildView(close_button_);
+
+ hover_animation_.reset(new SlideAnimation(this));
+ hover_animation_->SetSlideDuration(kHoverDurationMs);
+
+ pulse_animation_.reset(new ThrobAnimation(this));
+ pulse_animation_->SetSlideDuration(kPulseDurationMs);
+}
+
+TabRenderer::~TabRenderer() {
+ delete crash_animation_;
+}
+
+void TabRenderer::UpdateData(TabContents* contents) {
+ DCHECK(contents);
+ data_.favicon = contents->GetFavIcon();
+ data_.title = contents->GetTitle();
+ data_.loading = contents->is_loading();
+ data_.off_the_record = contents->profile()->IsOffTheRecord();
+ data_.show_icon = contents->ShouldDisplayFavIcon();
+ data_.show_download_icon = contents->IsDownloadShelfVisible();
+ data_.crashed = contents->IsCrashed();
+}
+
+void TabRenderer::UpdateFromModel() {
+ // Force a layout, since the tab may have grown a favicon.
+ Layout();
+ SchedulePaint();
+
+ if (data_.crashed) {
+ if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation())
+ StartCrashAnimation();
+ } else {
+ if (IsPerformingCrashAnimation())
+ StopCrashAnimation();
+ ResetCrashedFavIcon();
+ }
+}
+
+bool TabRenderer::IsSelected() const {
+ return true;
+}
+
+void TabRenderer::ValidateLoadingAnimation(AnimationState animation_state) {
+ if (animation_state_ != animation_state) {
+ // The waiting animation is the reverse of the loading animation, but at a
+ // different rate - the following reverses and scales the animation_frame_
+ // so that the frame is at an equivalent position when going from one
+ // animation to the other.
+ if (animation_state_ == ANIMATION_WAITING &&
+ animation_state == ANIMATION_LOADING) {
+ animation_frame_ = loading_animation_frame_count -
+ (animation_frame_ / waiting_to_loading_frame_count_ratio);
+ }
+ animation_state_ = animation_state;
+ }
+
+ if (animation_state_ != ANIMATION_NONE) {
+ animation_frame_ = ++animation_frame_ %
+ ((animation_state_ == ANIMATION_WAITING) ?
+ waiting_animation_frame_count :
+ loading_animation_frame_count);
+ } else {
+ animation_frame_ = 0;
+ }
+
+ SchedulePaint();
+}
+
+void TabRenderer::StartPulse() {
+ pulse_animation_->Reset();
+ pulse_animation_->StartThrobbing(std::numeric_limits<int>::max());
+}
+
+void TabRenderer::StopPulse() {
+ if (pulse_animation_->IsAnimating())
+ pulse_animation_->Stop();
+}
+
+// static
+gfx::Size TabRenderer::GetMinimumSize() {
+ InitResources();
+
+ gfx::Size minimum_size;
+ minimum_size.set_width(kLeftPadding + kRightPadding);
+ // Since we use bitmap images, the real minimum height of the image is
+ // defined most accurately by the height of the end cap images.
+ minimum_size.set_height(tab_active_l->height());
+ return minimum_size;
+}
+
+// static
+gfx::Size TabRenderer::GetMinimumSelectedSize() {
+ gfx::Size minimum_size = GetMinimumSize();
+ minimum_size.set_width(kLeftPadding + kFaviconSize + kRightPadding);
+ return minimum_size;
+}
+
+// static
+gfx::Size TabRenderer::GetStandardSize() {
+ gfx::Size standard_size = GetMinimumSize();
+ standard_size.set_width(
+ standard_size.width() + kFavIconTitleSpacing + kStandardTitleWidth);
+ return standard_size;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabRenderer, protected:
+
+std::wstring TabRenderer::GetTitle() const {
+ return data_.title;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabRenderer, ChromeViews::View overrides:
+
+void TabRenderer::Paint(ChromeCanvas* canvas) {
+ // Don't paint if we're narrower than we can render correctly. (This should
+ // only happen during animations).
+ if (GetWidth() < GetMinimumSize().width())
+ return;
+
+ // See if the model changes whether the icons should be painted.
+ const bool show_icon = ShouldShowIcon();
+ const bool show_download_icon = data_.show_download_icon;
+ const bool show_close_button = ShouldShowCloseBox();
+ if (show_icon != showing_icon_ ||
+ show_download_icon != showing_download_icon_ ||
+ show_close_button != showing_close_button_)
+ Layout();
+
+ PaintTabBackground(canvas);
+
+ // Paint the loading animation if the page is currently loading, otherwise
+ // show the page's favicon.
+ if (show_icon) {
+ if (animation_state_ != ANIMATION_NONE) {
+ PaintLoadingAnimation(canvas);
+ } else {
+ canvas->save();
+ canvas->ClipRectInt(0, 0, GetWidth(), GetHeight() - 4);
+ if (should_display_crashed_favicon_) {
+ canvas->DrawBitmapInt(*crashed_fav_icon, 0, 0,
+ crashed_fav_icon->width(),
+ crashed_fav_icon->height(),
+ favicon_bounds_.x(),
+ favicon_bounds_.y() + fav_icon_hiding_offset_,
+ kFaviconSize, kFaviconSize,
+ true);
+ } else {
+ if (!data_.favicon.isNull()) {
+ canvas->DrawBitmapInt(data_.favicon, 0, 0,
+ data_.favicon.width(),
+ data_.favicon.height(),
+ favicon_bounds_.x(),
+ favicon_bounds_.y() + fav_icon_hiding_offset_,
+ kFaviconSize, kFaviconSize,
+ true);
+ }
+ }
+ canvas->restore();
+ }
+ }
+
+ if (show_download_icon) {
+ canvas->DrawBitmapInt(*download_icon,
+ download_icon_bounds_.x(), download_icon_bounds_.y());
+ }
+
+ // Paint the Title.
+ std::wstring title = data_.title;
+ if (title.empty()) {
+ if (data_.loading) {
+ title = l10n_util::GetString(IDS_TAB_LOADING_TITLE);
+ } else {
+ title = l10n_util::GetString(IDS_TAB_UNTITLED_TITLE);
+ }
+ } else {
+ Browser::FormatTitleForDisplay(&title);
+ }
+
+ SkColor title_color = IsSelected() ? kSelectedTitleColor
+ : kUnselectedTitleColor;
+ canvas->DrawStringInt(title, title_font, title_color, title_bounds_.x(),
+ title_bounds_.y(), title_bounds_.width(),
+ title_bounds_.height());
+}
+
+void TabRenderer::Layout() {
+ CRect lb;
+ GetLocalBounds(&lb, false);
+ if (lb.IsRectEmpty())
+ return;
+
+ lb.left += kLeftPadding;
+ lb.top += kTopPadding;
+ lb.bottom -= kBottomPadding;
+ lb.right -= kRightPadding;
+
+ // First of all, figure out who is tallest.
+ int content_height = GetContentHeight();
+
+ // Size the Favicon.
+ showing_icon_ = ShouldShowIcon();
+ if (showing_icon_) {
+ int favicon_top = kTopPadding + (content_height - kFaviconSize) / 2;
+ favicon_bounds_.SetRect(lb.left, favicon_top, kFaviconSize, kFaviconSize);
+ } else {
+ favicon_bounds_.SetRect(lb.left, lb.top, 0, 0);
+ }
+
+ // Size the download icon.
+ showing_download_icon_ = data_.show_download_icon;
+ if (showing_download_icon_) {
+ int icon_top = kTopPadding + (content_height - download_icon_height) / 2;
+ download_icon_bounds_.SetRect(lb.Width() - download_icon_width, icon_top,
+ download_icon_width, download_icon_height);
+ }
+
+ // Size the Close button.
+ showing_close_button_ = ShouldShowCloseBox();
+ if (showing_close_button_) {
+ int close_button_top =
+ kTopPadding + kCloseButtonVertFuzz +
+ (content_height - close_button_height) / 2;
+ // If the ratio of the close button size to tab width exceeds the maximum.
+ close_button_->SetBounds(lb.Width() + kCloseButtonHorzFuzz,
+ close_button_top, close_button_width,
+ close_button_height);
+ close_button_->SetVisible(true);
+ } else {
+ close_button_->SetBounds(0, 0, 0, 0);
+ close_button_->SetVisible(false);
+ }
+
+ // Size the Title text to fill the remaining space.
+ int title_left = favicon_bounds_.right() + kFavIconTitleSpacing;
+ int title_top = kTopPadding + (content_height - title_font_height) / 2;
+
+ // If the user has big fonts, the title will appear rendered too far down on
+ // the y-axis if we use the regular top padding, so we need to adjust it so
+ // that the text appears centered.
+ gfx::Size minimum_size = GetMinimumSize();
+ int text_height = title_top + title_font_height + kBottomPadding;
+ if (text_height > minimum_size.height())
+ title_top -= (text_height - minimum_size.height()) / 2;
+
+ int title_width;
+ if (close_button_->IsVisible()) {
+ title_width = std::max(close_button_->GetX() -
+ kTitleCloseButtonSpacing - title_left, 0);
+ } else {
+ title_width = std::max(lb.Width() - title_left, 0);
+ }
+ if (data_.show_download_icon)
+ title_width = std::max(title_width - download_icon_width, 0);
+ title_bounds_.SetRect(title_left, title_top, title_width, title_font_height);
+
+ // Certain UI elements within the Tab (the favicon, the download icon, etc.)
+ // are not represented as child Views (which is the preferred method).
+ // Instead, these UI elements are drawn directly on the canvas from within
+ // Tab::Paint(). The Tab's child Views (for example, the Tab's close button
+ // which is a ChromeViews::Button instance) are automatically mirrored by the
+ // mirroring infrastructure in ChromeViews. The elements Tab draws directly
+ // on the canvas need to be manually mirrored if the View's layout is
+ // right-to-left.
+ favicon_bounds_.set_x(MirroredLeftPointForRect(favicon_bounds_));
+ title_bounds_.set_x(MirroredLeftPointForRect(title_bounds_));
+ download_icon_bounds_.set_x(MirroredLeftPointForRect(download_icon_bounds_));
+}
+
+void TabRenderer::DidChangeBounds(const CRect& previous,
+ const CRect& current) {
+ Layout();
+}
+
+
+void TabRenderer::OnMouseEntered(const ChromeViews::MouseEvent& e) {
+ hover_animation_->SetTweenType(SlideAnimation::EASE_OUT);
+ hover_animation_->Show();
+}
+
+void TabRenderer::OnMouseExited(const ChromeViews::MouseEvent& e) {
+ hover_animation_->SetTweenType(SlideAnimation::EASE_IN);
+ hover_animation_->Hide();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TabRenderer, AnimationDelegate implementation:
+
+void TabRenderer::AnimationProgressed(const Animation* animation) {
+ SchedulePaint();
+}
+
+void TabRenderer::AnimationCanceled(const Animation* animation) {
+ AnimationEnded(animation);
+}
+
+void TabRenderer::AnimationEnded(const Animation* animation) {
+ SchedulePaint();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabRenderer, private
+
+void TabRenderer::PaintTabBackground(ChromeCanvas* canvas) {
+ if (IsSelected()) {
+ // Sometimes detaching a tab quickly can result in the model reporting it
+ // as not being selected, so is_drag_clone_ ensures that we always paint
+ // the active representation for the dragged tab.
+ PaintActiveTabBackground(canvas);
+ } else {
+ // Draw our hover state.
+ Animation* animation = hover_animation_.get();
+ if (pulse_animation_->IsAnimating())
+ animation = pulse_animation_.get();
+ if (animation->GetCurrentValue() > 0) {
+ PaintHoverTabBackground(canvas, animation->GetCurrentValue() *
+ (win_util::ShouldUseVistaFrame() ?
+ kHoverOpacityVista : kHoverOpacity));
+ } else {
+ PaintInactiveTabBackground(canvas);
+ }
+ }
+}
+
+void TabRenderer::PaintInactiveTabBackground(ChromeCanvas* canvas) {
+ bool is_otr = data_.off_the_record;
+ canvas->DrawBitmapInt(is_otr ? *tab_inactive_otr_l : *tab_inactive_l, 0, 0);
+ canvas->TileImageInt(is_otr ? *tab_inactive_otr_c : *tab_inactive_c,
+ tab_inactive_l_width, 0,
+ GetWidth() - tab_inactive_l_width - tab_inactive_r_width,
+ GetHeight());
+ canvas->DrawBitmapInt(is_otr ? *tab_inactive_otr_r : *tab_inactive_r,
+ GetWidth() - tab_inactive_r_width, 0);
+}
+
+void TabRenderer::PaintActiveTabBackground(ChromeCanvas* canvas) {
+ canvas->DrawBitmapInt(*tab_active_l, 0, 0);
+ canvas->TileImageInt(*tab_active_c, tab_active_l_width, 0,
+ GetWidth() - tab_active_l_width - tab_active_r_width, GetHeight());
+ canvas->DrawBitmapInt(*tab_active_r, GetWidth() - tab_active_r_width, 0);
+}
+
+void TabRenderer::PaintHoverTabBackground(ChromeCanvas* canvas,
+ double opacity) {
+ bool is_otr = data_.off_the_record;
+ SkBitmap left = gfx::ImageOperations::CreateBlendedBitmap(
+ (is_otr ? *tab_inactive_otr_l : *tab_inactive_l),
+ *tab_hover_l, opacity);
+ SkBitmap center = gfx::ImageOperations::CreateBlendedBitmap(
+ (is_otr ? *tab_inactive_otr_c : *tab_inactive_c),
+ *tab_hover_c, opacity);
+ SkBitmap right = gfx::ImageOperations::CreateBlendedBitmap(
+ (is_otr ? *tab_inactive_otr_r : *tab_inactive_r),
+ *tab_hover_r, opacity);
+
+ canvas->DrawBitmapInt(left, 0, 0);
+ canvas->TileImageInt(center, tab_active_l_width, 0,
+ GetWidth() - tab_active_l_width - tab_active_r_width, GetHeight());
+ canvas->DrawBitmapInt(right, GetWidth() - tab_active_r_width, 0);
+}
+
+void TabRenderer::PaintLoadingAnimation(ChromeCanvas* canvas) {
+ SkBitmap* frames = (animation_state_ == ANIMATION_WAITING) ?
+ waiting_animation_frames : loading_animation_frames;
+ int image_size = frames->height();
+ int image_offset = animation_frame_ * image_size;
+ int dst_y = (GetHeight() - image_size) / 2;
+
+ // Just like with the Tab's title and favicon, the position for the page
+ // loading animation also needs to be mirrored if the View's UI layout is
+ // right-to-left.
+ int dst_x;
+ if (UILayoutIsRightToLeft()) {
+ dst_x = GetWidth() - kLeftPadding - image_size;
+ } else {
+ dst_x = kLeftPadding;
+ }
+ canvas->DrawBitmapInt(*frames, image_offset, 0, image_size,
+ image_size, dst_x, dst_y, image_size, image_size,
+ false);
+}
+
+int TabRenderer::IconCapacity() const {
+ if (GetHeight() < GetMinimumSize().height()) {
+ return 0;
+ }
+ return (GetWidth() - kLeftPadding - kRightPadding) / kFaviconSize;
+}
+
+bool TabRenderer::ShouldShowIcon() const {
+ if (!data_.show_icon) {
+ return false;
+ } else if (IsSelected()) {
+ // The selected tab clips favicon before close button.
+ return IconCapacity() >= 2;
+ }
+ // Non-selected tabs clip close button before favicon.
+ return IconCapacity() >= 1;
+}
+
+bool TabRenderer::ShouldShowCloseBox() const {
+ // The selected tab never clips close button.
+ return IsSelected() || IconCapacity() >= 3;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabRenderer, private:
+
+void TabRenderer::StartCrashAnimation() {
+ if (!crash_animation_)
+ crash_animation_ = new FavIconCrashAnimation(this);
+ crash_animation_->Reset();
+ crash_animation_->Start();
+}
+
+void TabRenderer::StopCrashAnimation() {
+ if (!crash_animation_)
+ return;
+ crash_animation_->Stop();
+}
+
+bool TabRenderer::IsPerformingCrashAnimation() const {
+ return crash_animation_ && crash_animation_->IsAnimating();
+}
+
+void TabRenderer::SetFavIconHidingOffset(int offset) {
+ fav_icon_hiding_offset_ = offset;
+ SchedulePaint();
+}
+
+void TabRenderer::DisplayCrashedFavIcon() {
+ should_display_crashed_favicon_ = true;
+}
+
+void TabRenderer::ResetCrashedFavIcon() {
+ should_display_crashed_favicon_ = false;
+}
+
diff --git a/chrome/browser/views/tabs/tab_renderer.h b/chrome/browser/views/tabs/tab_renderer.h
new file mode 100644
index 0000000..cb92574
--- /dev/null
+++ b/chrome/browser/views/tabs/tab_renderer.h
@@ -0,0 +1,174 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef CHROME_BROWSER_VIEWS_TABS_TAB_RENDERER_H__
+#define CHROME_BROWSER_VIEWS_TABS_TAB_RENDERER_H__
+
+#include "base/gfx/point.h"
+#include "chrome/common/animation.h"
+#include "chrome/common/slide_animation.h"
+#include "chrome/common/throb_animation.h"
+#include "chrome/views/button.h"
+#include "chrome/views/menu.h"
+#include "chrome/views/view.h"
+
+class TabContents;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// TabRenderer
+//
+// A View that renders a Tab, either in a TabStrip or in a DraggedTabView.
+//
+///////////////////////////////////////////////////////////////////////////////
+class TabRenderer : public ChromeViews::View,
+ public AnimationDelegate {
+ public:
+ // Possible animation states.
+ enum AnimationState {
+ ANIMATION_NONE,
+ ANIMATION_WAITING,
+ ANIMATION_LOADING
+ };
+
+ TabRenderer();
+ virtual ~TabRenderer();
+
+ // Updates the data the Tab uses to render itself from the specified
+ // TabContents.
+ void UpdateData(TabContents* contents);
+
+ // Updates the display to reflect the contents of this TabRenderer's model.
+ void UpdateFromModel();
+
+ // Returns true if the Tab is selected, false otherwise.
+ virtual bool IsSelected() const;
+
+ // Advance the Loading Animation to the next frame, or hide the animation if
+ // the tab isn't loading.
+ void ValidateLoadingAnimation(AnimationState animation_state);
+
+ // Starts/Stops a pulse animation.
+ void StartPulse();
+ void StopPulse();
+
+ // Returns the minimum possible size of a single unselected Tab.
+ static gfx::Size GetMinimumSize();
+ // Returns the minimum possible size of a selected Tab. Selected tabs must
+ // always show a close button and have a larger minimum size than unselected
+ // tabs.
+ static gfx::Size GetMinimumSelectedSize();
+ // Returns the preferred size of a single Tab, assuming space is
+ // available.
+ static gfx::Size GetStandardSize();
+
+ protected:
+ ChromeViews::Button* close_button() const { return close_button_; }
+ const gfx::Rect& title_bounds() const { return title_bounds_; }
+
+ // Returns the title of the Tab.
+ std::wstring GetTitle() const;
+
+ private:
+ // Overridden from ChromeViews::View:
+ virtual void Paint(ChromeCanvas* canvas);
+ virtual void Layout();
+ virtual void DidChangeBounds(const CRect& previous, const CRect& current);
+ virtual void OnMouseEntered(const ChromeViews::MouseEvent& event);
+ virtual void OnMouseExited(const ChromeViews::MouseEvent& event);
+
+ // Overridden from AnimationDelegate:
+ virtual void AnimationProgressed(const Animation* animation);
+ virtual void AnimationCanceled(const Animation* animation);
+ virtual void AnimationEnded(const Animation* animation);
+
+ // Starts/Stops the crash animation.
+ void StartCrashAnimation();
+ void StopCrashAnimation();
+
+ // Return true if the crash animation is currently running.
+ bool IsPerformingCrashAnimation() const;
+
+ // Set the temporary offset for the favicon. This is used during animation.
+ void SetFavIconHidingOffset(int offset);
+
+ void DisplayCrashedFavIcon();
+ void ResetCrashedFavIcon();
+
+ // Paint various portions of the Tab
+ void PaintTabBackground(ChromeCanvas* canvas);
+ void PaintInactiveTabBackground(ChromeCanvas* canvas);
+ void PaintActiveTabBackground(ChromeCanvas* canvas);
+ void PaintHoverTabBackground(ChromeCanvas* canvas, double opacity);
+ void PaintLoadingAnimation(ChromeCanvas* canvas);
+
+ // Returns the number of favicon-size elements that can fit in the tab's
+ // current size.
+ int IconCapacity() const;
+
+ // Returns whether the Tab should display a favicon.
+ bool ShouldShowIcon() const;
+
+ // Returns whether the Tab should display a close button.
+ bool ShouldShowCloseBox() const;
+
+ // The bounds of various sections of the display.
+ gfx::Rect favicon_bounds_;
+ gfx::Rect download_icon_bounds_;
+ gfx::Rect title_bounds_;
+
+ // Current state of the animation.
+ AnimationState animation_state_;
+
+ // The current index into the Animation image strip.
+ int animation_frame_;
+
+ // Close Button.
+ ChromeViews::Button* close_button_;
+
+ // Hover animation.
+ scoped_ptr<SlideAnimation> hover_animation_;
+
+ // Pulse animation.
+ scoped_ptr<ThrobAnimation> pulse_animation_;
+
+ // Model data. We store this here so that we don't need to ask the underlying
+ // model, which is tricky since instances of this object can outlive the
+ // corresponding objects in the underlying model.
+ struct TabData {
+ SkBitmap favicon;
+ std::wstring title;
+ bool loading;
+ bool crashed;
+ bool off_the_record;
+ bool show_icon;
+ bool show_download_icon;
+ };
+ TabData data_;
+
+ // Whether we're showing the icon. It is cached so that we can detect when it
+ // changes and layout appropriately.
+ bool showing_icon_;
+
+ // Whether we are showing the download icon. Comes from the model.
+ bool showing_download_icon_;
+
+ // Whether we are showing the close button. It is cached so that we can
+ // detect when it changes and layout appropriately.
+ bool showing_close_button_;
+
+ // The offset used to animate the favicon location.
+ int fav_icon_hiding_offset_;
+
+ // The animation object used to swap the favicon with the sad tab icon.
+ class FavIconCrashAnimation;
+ FavIconCrashAnimation* crash_animation_;
+
+ bool should_display_crashed_favicon_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TabRenderer);
+};
+
+#endif // CHROME_BROWSER_VIEWS_TABS_TAB_RENDERER_H__
+
diff --git a/chrome/browser/views/tabs/tab_strip.cc b/chrome/browser/views/tabs/tab_strip.cc
new file mode 100644
index 0000000..2b8dc61
--- /dev/null
+++ b/chrome/browser/views/tabs/tab_strip.cc
@@ -0,0 +1,1524 @@
+// Copyright (c) 2006-2008 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.h"
+
+#include "base/gfx/size.h"
+#include "chrome/app/theme/theme_resources.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/tab_contents.h"
+#include "chrome/browser/tabs/tab_strip_model.h"
+#include "chrome/browser/user_metrics.h"
+#include "chrome/browser/view_ids.h"
+#include "chrome/browser/views/tabs/dragged_tab_controller.h"
+#include "chrome/browser/views/tabs/tab.h"
+#include "chrome/browser/vista_frame.h"
+#include "chrome/browser/web_contents.h"
+#include "chrome/common/drag_drop_types.h"
+#include "chrome/common/gfx/chrome_canvas.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/os_exchange_data.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/resource_bundle.h"
+#include "chrome/common/slide_animation.h"
+#include "chrome/common/stl_util-inl.h"
+#include "chrome/common/win_util.h"
+#include "chrome/views/image_view.h"
+#include "chrome/views/painter.h"
+
+#include "generated_resources.h"
+
+#undef min
+#undef max
+
+using ChromeViews::DropTargetEvent;
+
+static const int kDefaultAnimationDurationMs = 100;
+static const int kResizeLayoutAnimationDurationMs = 166;
+static const int kReorderAnimationDurationMs = 166;
+
+static const int kLoadingAnimationFrameTimeMs = 30;
+static const int kNewTabButtonHOffset = -5;
+static const int kNewTabButtonVOffset = 5;
+static const int kResizeTabsTimeMs = 300;
+static const int kSuspendAnimationsTimeMs = 200;
+static const int kTabHOffset = -16;
+static const int kTabStripAnimationVSlop = 40;
+
+// Size of the drop indicator.
+static int drop_indicator_width;
+static int drop_indicator_height;
+
+static inline int Round(double x) {
+ // Why oh why is this not in a standard header?
+ return static_cast<int>(floor(x + 0.5));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// TabAnimation
+//
+// A base class for all TabStrip animations.
+//
+class TabStrip::TabAnimation : public AnimationDelegate {
+ public:
+ friend class TabStrip;
+
+ // Possible types of animation.
+ enum Type {
+ INSERT,
+ REMOVE,
+ MOVE,
+ RESIZE
+ };
+
+ TabAnimation(TabStrip* tabstrip, Type type)
+ : tabstrip_(tabstrip),
+ animation_(this),
+ start_selected_width_(0),
+ start_unselected_width_(0),
+ end_selected_width_(0),
+ end_unselected_width_(0),
+ layout_on_completion_(false),
+ type_(type) {
+ }
+ virtual ~TabAnimation() {}
+
+ Type type() const { return type_; }
+
+ void Start() {
+ animation_.SetSlideDuration(GetDuration());
+ animation_.SetTweenType(SlideAnimation::EASE_OUT);
+ if (!animation_.IsShowing()) {
+ animation_.Reset();
+ animation_.Show();
+ }
+ }
+
+ void Stop() {
+ animation_.Stop();
+ }
+
+ void set_layout_on_completion(bool layout_on_completion) {
+ layout_on_completion_ = layout_on_completion;
+ }
+
+ // Retrieves the width for the Tab at the specified index if an animation is
+ // active.
+ static double GetCurrentTabWidth(TabStrip* tabstrip,
+ TabStrip::TabAnimation* animation,
+ int index) {
+ double unselected, selected;
+ tabstrip->GetCurrentTabWidths(&unselected, &selected);
+ Tab* tab = tabstrip->GetTabAt(index);
+ double tab_width = tab->IsSelected() ? selected : unselected;
+ if (animation) {
+ double specified_tab_width = animation->GetWidthForTab(index);
+ if (specified_tab_width != -1)
+ tab_width = specified_tab_width;
+ }
+ return tab_width;
+ }
+
+ // Overridden from AnimationDelegate:
+ virtual void AnimationProgressed(const Animation* animation) {
+ tabstrip_->AnimationLayout(end_unselected_width_);
+ }
+
+ virtual void AnimationEnded(const Animation* animation) {
+ tabstrip_->FinishAnimation(this, layout_on_completion_);
+ // This object is destroyed now, so we can't do anything else after this.
+ }
+
+ virtual void AnimationCanceled(const Animation* animation) {
+ AnimationEnded(animation);
+ }
+
+ protected:
+ // Returns the duration of the animation.
+ virtual int GetDuration() const {
+ return kDefaultAnimationDurationMs;
+ }
+
+ // Subclasses override to return the width of the Tab at the specified index
+ // at the current animation frame. -1 indicates the default width should be
+ // used for the Tab.
+ virtual double GetWidthForTab(int index) const {
+ return -1; // Use default.
+ }
+
+ // Figure out the desired start and end widths for the specified pre- and
+ // post- animation tab counts.
+ void GenerateStartAndEndWidths(int start_tab_count, int end_tab_count) {
+ tabstrip_->GetDesiredTabWidths(start_tab_count, &start_unselected_width_,
+ &start_selected_width_);
+ double standard_tab_width =
+ static_cast<double>(TabRenderer::GetStandardSize().width());
+ if (start_tab_count < end_tab_count &&
+ start_unselected_width_ < standard_tab_width) {
+ double minimum_tab_width =
+ static_cast<double>(TabRenderer::GetMinimumSize().width());
+ start_unselected_width_ -= minimum_tab_width / start_tab_count;
+ }
+ tabstrip_->GenerateIdealBounds();
+ tabstrip_->GetDesiredTabWidths(end_tab_count,
+ &end_unselected_width_,
+ &end_selected_width_);
+ }
+
+ TabStrip* tabstrip_;
+ SlideAnimation animation_;
+
+ double start_selected_width_;
+ double start_unselected_width_;
+ double end_selected_width_;
+ double end_unselected_width_;
+
+ private:
+ // True if a complete re-layout is required upon completion of the animation.
+ // Subclasses set this if they don't perform a complete layout
+ // themselves and canceling the animation may leave the strip in an
+ // inconsistent state.
+ bool layout_on_completion_;
+
+ const Type type_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TabAnimation);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Handles insertion of a Tab at |index|.
+class InsertTabAnimation : public TabStrip::TabAnimation {
+ public:
+ explicit InsertTabAnimation(TabStrip* tabstrip, int index)
+ : TabAnimation(tabstrip, INSERT),
+ index_(index) {
+ int tab_count = tabstrip->GetTabCount();
+ GenerateStartAndEndWidths(tab_count - 1, tab_count);
+ }
+ virtual ~InsertTabAnimation() {}
+
+ protected:
+ // Overridden from TabStrip::TabAnimation:
+ virtual double GetWidthForTab(int index) const {
+ if (index == index_) {
+ bool is_selected = tabstrip_->model()->selected_index() == index;
+ double target_width =
+ is_selected ? end_unselected_width_ : end_selected_width_;
+ double start_width = is_selected ? Tab::GetMinimumSelectedSize().width() :
+ Tab::GetMinimumSize().width();
+ double delta = target_width - start_width;
+ if (delta > 0)
+ return start_width + (delta * animation_.GetCurrentValue());
+ return start_width;
+ }
+ if (tabstrip_->GetTabAt(index)->IsSelected()) {
+ double delta = end_selected_width_ - start_selected_width_;
+ return start_selected_width_ + (delta * animation_.GetCurrentValue());
+ }
+ double delta = end_unselected_width_ - start_unselected_width_;
+ return start_unselected_width_ + (delta * animation_.GetCurrentValue());
+ }
+
+ private:
+ int index_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(InsertTabAnimation);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Handles removal of a Tab from |index|
+class RemoveTabAnimation : public TabStrip::TabAnimation {
+ public:
+ RemoveTabAnimation(TabStrip* tabstrip, int index, TabContents* contents)
+ : TabAnimation(tabstrip, REMOVE),
+ index_(index) {
+ int tab_count = tabstrip->GetTabCount();
+ GenerateStartAndEndWidths(tab_count, tab_count - 1);
+ }
+
+ // Returns the index of the tab being removed.
+ int index() const { return index_; }
+
+ virtual ~RemoveTabAnimation() {
+ }
+
+ protected:
+ // Overridden from TabStrip::TabAnimation:
+ virtual double GetWidthForTab(int index) const {
+ Tab* tab = tabstrip_->GetTabAt(index);
+ if (index == index_) {
+ // The tab(s) being removed are gradually shrunken depending on the state
+ // of the animation.
+ // Removed animated Tabs are never selected.
+ double start_width = start_unselected_width_;
+ double target_width = Tab::GetMinimumSize().width() + kTabHOffset;
+ double delta = start_width - target_width;
+ return start_width - (delta * animation_.GetCurrentValue());
+ }
+ if (tabstrip_->available_width_for_tabs_ != -1 &&
+ index_ != tabstrip_->GetTabCount() - 1) {
+ return TabStrip::TabAnimation::GetWidthForTab(index);
+ }
+ // All other tabs are sized according to the start/end widths specified at
+ // the start of the animation.
+ if (tab->IsSelected()) {
+ double delta = end_selected_width_ - start_selected_width_;
+ return start_selected_width_ + (delta * animation_.GetCurrentValue());
+ }
+ double delta = end_unselected_width_ - start_unselected_width_;
+ return start_unselected_width_ + (delta * animation_.GetCurrentValue());
+ }
+
+ virtual void AnimationEnded(const Animation* animation) {
+ RemoveTabAt(index_);
+ HighlightCloseButton();
+ TabStrip::TabAnimation::AnimationEnded(animation);
+ }
+
+ private:
+ // Cleans up the Tab from the TabStrip at the specified |index| once its
+ // animated removal is complete.
+ void RemoveTabAt(int index) const {
+ // Save a pointer to the Tab before we remove the TabData, we'll need this
+ // later.
+ Tab* removed = tabstrip_->tab_data_.at(index).tab;
+
+ // Remove the Tab from the TabStrip's list...
+ tabstrip_->tab_data_.erase(tabstrip_->tab_data_.begin() + index);
+
+ // If the TabContents being detached was removed as a result of a drag
+ // gesture from its corresponding Tab, we don't want to remove the Tab from
+ // the child list, because if we do so it'll stop receiving events and the
+ // drag will stall. So we only remove if a drag isn't active, or the Tab
+ // was for some other TabContents.
+ if (!tabstrip_->IsDragSessionActive() ||
+ !tabstrip_->drag_controller_->IsDragSourceTab(removed)) {
+ tabstrip_->RemoveChildView(removed);
+ delete removed;
+ }
+ }
+
+ // When the animation completes, we send the ViewContainer a message to
+ // simulate a mouse moved event at the current mouse position. This tickles
+ // the Tab the mouse is currently over to show the "hot" state of the close
+ // button.
+ void HighlightCloseButton() {
+ if (tabstrip_->available_width_for_tabs_ == -1 ||
+ tabstrip_->IsDragSessionActive()) {
+ // This function is not required (and indeed may crash!) for removes
+ // spawned by non-mouse closes and drag-detaches.
+ return;
+ }
+
+ POINT pt;
+ GetCursorPos(&pt);
+ ChromeViews::ViewContainer* vc = tabstrip_->GetViewContainer();
+ RECT wr;
+ GetWindowRect(vc->GetHWND(), &wr);
+ pt.x -= wr.left;
+ pt.y -= wr.top;
+
+ // Return to message loop - otherwise we may disrupt some operation that's
+ // in progress.
+ PostMessage(vc->GetHWND(), WM_MOUSEMOVE, 0, MAKELPARAM(pt.x, pt.y));
+ }
+
+ int index_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(RemoveTabAnimation);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Handles the movement of a Tab from one position to another.
+class MoveTabAnimation : public TabStrip::TabAnimation {
+ public:
+ MoveTabAnimation(TabStrip* tabstrip, int tab_a_index, int tab_b_index)
+ : TabAnimation(tabstrip, MOVE),
+ start_tab_a_bounds_(tabstrip_->GetIdealBounds(tab_b_index)),
+ start_tab_b_bounds_(tabstrip_->GetIdealBounds(tab_a_index)) {
+ tab_a_ = tabstrip_->GetTabAt(tab_a_index);
+ tab_b_ = tabstrip_->GetTabAt(tab_b_index);
+
+ // Since we don't do a full TabStrip re-layout, we need to force a full
+ // layout upon completion since we're not guaranteed to be in a good state
+ // if for example the animation is canceled.
+ set_layout_on_completion(true);
+ }
+ virtual ~MoveTabAnimation() {}
+
+ // Overridden from AnimationDelegate:
+ virtual void AnimationProgressed(const Animation* animation) {
+ // Position Tab A
+ double distance = start_tab_b_bounds_.x() - start_tab_a_bounds_.x();
+ double delta = distance * animation_.GetCurrentValue();
+ double new_x = start_tab_a_bounds_.x() + delta;
+ tab_a_->SetBounds(Round(new_x), tab_a_->GetY(), tab_a_->GetWidth(),
+ tab_a_->GetHeight());
+
+ // Position Tab B
+ distance = start_tab_a_bounds_.x() - start_tab_b_bounds_.x();
+ delta = distance * animation_.GetCurrentValue();
+ new_x = start_tab_b_bounds_.x() + delta;
+ tab_b_->SetBounds(Round(new_x), tab_b_->GetY(), tab_b_->GetWidth(),
+ tab_b_->GetHeight());
+
+ tabstrip_->SchedulePaint();
+ }
+
+ protected:
+ // Overridden from TabStrip::TabAnimation:
+ virtual int GetDuration() const { return kReorderAnimationDurationMs; }
+
+ private:
+ // The two tabs being exchanged.
+ Tab* tab_a_;
+ Tab* tab_b_;
+
+ // ...and their bounds.
+ gfx::Rect start_tab_a_bounds_;
+ gfx::Rect start_tab_b_bounds_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MoveTabAnimation);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Handles the animated resize layout of the entire TabStrip from one width
+// to another.
+class ResizeLayoutAnimation : public TabStrip::TabAnimation {
+ public:
+ explicit ResizeLayoutAnimation(TabStrip* tabstrip)
+ : TabAnimation(tabstrip, RESIZE) {
+ int tab_count = tabstrip->GetTabCount();
+ GenerateStartAndEndWidths(tab_count, tab_count);
+ InitStartState();
+ }
+ virtual ~ResizeLayoutAnimation() {
+ }
+
+ // Overridden from AnimationDelegate:
+ virtual void AnimationEnded(const Animation* animation) {
+ tabstrip_->resize_layout_scheduled_ = false;
+ TabStrip::TabAnimation::AnimationEnded(animation);
+ }
+
+ protected:
+ // Overridden from TabStrip::TabAnimation:
+ virtual int GetDuration() const {
+ return kResizeLayoutAnimationDurationMs;
+ }
+
+ virtual double GetWidthForTab(int index) const {
+ if (tabstrip_->GetTabAt(index)->IsSelected()) {
+ double delta = end_selected_width_ - start_selected_width_;
+ return start_selected_width_ + (delta * animation_.GetCurrentValue());
+ }
+ double delta = end_unselected_width_ - start_unselected_width_;
+ return start_unselected_width_ + (delta * animation_.GetCurrentValue());
+ }
+
+ private:
+ // We need to start from the current widths of the Tabs as they were last
+ // laid out, _not_ the last known good state, which is what'll be done if we
+ // don't measure the Tab sizes here and just go with the default TabAnimation
+ // behavior...
+ void InitStartState() {
+ for (int i = 0; i < tabstrip_->GetTabCount(); ++i) {
+ Tab* current_tab = tabstrip_->GetTabAt(i);
+ if (current_tab->IsSelected()) {
+ start_selected_width_ = current_tab->GetWidth();
+ } else {
+ start_unselected_width_ = current_tab->GetWidth();
+ }
+ }
+ }
+
+ DISALLOW_EVIL_CONSTRUCTORS(ResizeLayoutAnimation);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// TabStrip, public:
+
+TabStrip::TabStrip(TabStripModel* model)
+ : model_(model),
+ resize_layout_factory_(this),
+ added_as_message_loop_observer_(false),
+ resize_layout_scheduled_(false),
+ current_unselected_width_(Tab::GetStandardSize().width()),
+ current_selected_width_(Tab::GetStandardSize().width()),
+ available_width_for_tabs_(-1) {
+ Init();
+}
+
+TabStrip::~TabStrip() {
+ // TODO(beng): (1031854) Restore this line once XPFrame/VistaFrame are dead.
+ //model_->RemoveObserver(this);
+
+ // TODO(beng): remove this if it doesn't work to fix the TabSelectedAt bug.
+ drag_controller_.reset(NULL);
+
+ // Make sure we unhook ourselves as a message loop observer so that we don't
+ // crash in the case where the user closes the window after closing a tab
+ // but before moving the mouse.
+ RemoveMessageLoopObserver();
+}
+
+int TabStrip::GetPreferredHeight() {
+ CSize preferred_size;
+ GetPreferredSize(&preferred_size);
+ return preferred_size.cy;
+}
+
+bool TabStrip::HasAvailableDragActions() const {
+ return model_->delegate()->GetDragActions() != 0;
+}
+
+void TabStrip::ShowApplicationMenu(const gfx::Point& p) {
+ TabStripModelDelegate* delegate = model_->delegate();
+ if (delegate)
+ delegate->ShowApplicationMenu(p);
+}
+
+bool TabStrip::CanProcessInputEvents() const {
+ return IsAnimating() == NULL;
+}
+
+bool TabStrip::PointIsWithinWindowCaption(const CPoint& point) {
+ ChromeViews::View* v = GetViewForPoint(point);
+
+ // If there is no control at this location, claim the hit was in the title
+ // bar to get a move action.
+ if (v == this)
+ return true;
+
+ // If the point is within the bounds of a Tab, the point can be considered
+ // part of the caption if there are no available drag operations for the Tab.
+ if (v->GetClassName() == Tab::kTabClassName && !HasAvailableDragActions())
+ return true;
+
+ // All other regions, including the new Tab button, should be considered part
+ // of the containing Window's client area so that regular events can be
+ // processed for them.
+ return false;
+}
+
+bool TabStrip::IsCompatibleWith(TabStrip* other) {
+ return model_->profile() == other->model()->profile();
+}
+
+bool TabStrip::IsAnimating() const {
+ return active_animation_.get() != NULL;
+}
+
+void TabStrip::DestroyDragController() {
+ if (IsDragSessionActive())
+ drag_controller_.reset(NULL);
+}
+
+void TabStrip::DestroyDraggedSourceTab(Tab* tab) {
+ // We could be running an animation that references this Tab.
+ if (active_animation_.get())
+ active_animation_->Stop();
+ // Make sure we leave the tab_data_ vector in a consistent state, otherwise
+ // we'll be pointing to tabs that have been deleted and removed from the
+ // child view list.
+ std::vector<TabData>::iterator it = tab_data_.begin();
+ for (; it != tab_data_.end(); ++it) {
+ if (it->tab == tab) {
+ NOTREACHED() << "Leaving in an inconsistent state!";
+ tab_data_.erase(it);
+ break;
+ }
+ }
+ tab->GetParent()->RemoveChildView(tab);
+ delete tab;
+ // Force a layout here, because if we've just quickly drag detached a Tab,
+ // the stopping of the active animation above may have left the TabStrip in a
+ // bad (visual) state.
+ Layout();
+}
+
+gfx::Rect TabStrip::GetIdealBounds(int index) {
+ DCHECK(index >= 0 && index < GetTabCount());
+ return tab_data_.at(index).ideal_bounds;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TabStrip, ChromeViews::View overrides:
+
+void TabStrip::PaintChildren(ChromeCanvas* canvas) {
+ // Paint the tabs in reverse order, so they stack to the left.
+ Tab* selected_tab = NULL;
+ for (int i = GetTabCount() - 1; i >= 0; --i) {
+ Tab* 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 (!tab->IsSelected()) {
+ tab->ProcessPaint(canvas);
+ } else {
+ selected_tab = tab;
+ }
+ }
+
+ if (win_util::ShouldUseVistaFrame()) {
+ // Make sure unselected tabs are somewhat transparent.
+ SkPaint paint;
+ paint.setColor(SkColorSetARGB(200, 255, 255, 255));
+ paint.setPorterDuffXfermode(SkPorterDuff::kDstIn_Mode);
+ paint.setStyle(SkPaint::kFill_Style);
+ canvas->FillRectInt(
+ 0, 0, GetWidth(),
+ GetHeight() - 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);
+
+ // Paint the New Tab button.
+ newtab_button_->ProcessPaint(canvas);
+}
+
+void TabStrip::DidChangeBounds(const CRect& prev, const CRect& curr) {
+ Layout();
+}
+
+// Overridden to support automation. See automation_proxy_uitest.cc.
+ChromeViews::View* TabStrip::GetViewByID(int view_id) const {
+ if (GetTabCount() > 0) {
+ if (view_id == VIEW_ID_TAB_LAST) {
+ return GetTabAt(GetTabCount() - 1);
+ } else if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) {
+ int index = view_id - VIEW_ID_TAB_0;
+ if (index >= 0 && index < GetTabCount()) {
+ return GetTabAt(index);
+ } else {
+ return NULL;
+ }
+ }
+ }
+
+ return View::GetViewByID(view_id);
+}
+
+void TabStrip::Layout() {
+ // Called from:
+ // - window resize
+ // - animation completion
+ if (active_animation_.get())
+ active_animation_->Stop();
+ GenerateIdealBounds();
+ int tab_count = GetTabCount();
+ int tab_right = 0;
+ for (int i = 0; i < tab_count; ++i) {
+ const gfx::Rect& bounds = tab_data_.at(i).ideal_bounds;
+ GetTabAt(i)->SetBounds(bounds.x(), bounds.y(), bounds.width(),
+ bounds.height());
+ tab_right = bounds.right() + kTabHOffset;
+ }
+ LayoutNewTabButton(static_cast<double>(tab_right), current_unselected_width_);
+ SchedulePaint();
+}
+
+void TabStrip::GetPreferredSize(CSize* preferred_size) {
+ DCHECK(preferred_size);
+ preferred_size->cx = 0;
+ preferred_size->cy = Tab::GetMinimumSize().height();
+}
+
+void TabStrip::OnDragEntered(const DropTargetEvent& event) {
+ UpdateDropIndex(event);
+}
+
+int TabStrip::OnDragUpdated(const DropTargetEvent& event) {
+ UpdateDropIndex(event);
+ return GetDropEffect(event);
+}
+
+void TabStrip::OnDragExited() {
+ SetDropIndex(-1, false);
+}
+
+int TabStrip::OnPerformDrop(const DropTargetEvent& event) {
+ if (!drop_info_.get())
+ return DragDropTypes::DRAG_NONE;
+
+ const int drop_index = drop_info_->drop_index;
+ const bool drop_before = drop_info_->drop_before;
+
+ // Hide the drop indicator.
+ SetDropIndex(-1, false);
+
+ GURL url;
+ std::wstring title;
+ if (!event.GetData().GetURLAndTitle(&url, &title) || !url.is_valid())
+ return DragDropTypes::DRAG_NONE;
+
+ if (drop_before) {
+ UserMetrics::RecordAction(L"Tab_DropURLBetweenTabs", model_->profile());
+
+ // Insert a new tab.
+ TabContents* contents =
+ model_->delegate()->CreateTabContentsForURL(
+ url, model_->profile(), PageTransition::TYPED, false, NULL);
+ model_->AddTabContents(contents, drop_index, PageTransition::GENERATED,
+ true);
+ } else {
+ UserMetrics::RecordAction(L"Tab_DropURLOnTab", model_->profile());
+
+ model_->GetTabContentsAt(drop_index)->controller()->
+ LoadURL(url, PageTransition::GENERATED);
+ model_->SelectTabContentsAt(drop_index, true);
+ }
+
+ return GetDropEffect(event);
+}
+
+bool TabStrip::GetAccessibleRole(VARIANT* role) {
+ DCHECK(role);
+
+ role->vt = VT_I4;
+ role->lVal = ROLE_SYSTEM_GROUPING;
+ return true;
+}
+
+bool TabStrip::GetAccessibleName(std::wstring* name) {
+ if (!accessible_name_.empty()) {
+ (*name).assign(accessible_name_);
+ return true;
+ }
+ return false;
+}
+
+void TabStrip::SetAccessibleName(const std::wstring& name) {
+ accessible_name_.assign(name);
+}
+
+ChromeViews::View* TabStrip::GetViewForPoint(const CPoint& point) {
+ return GetViewForPoint(point, false);
+}
+
+ChromeViews::View* TabStrip::GetViewForPoint(const CPoint& point,
+ bool can_create_floating) {
+ // Return any view that isn't a Tab or this TabStrip immediately. We don't
+ // want to interfere.
+ ChromeViews::View* v = View::GetViewForPoint(point, can_create_floating);
+ if (v && v != this && v->GetClassName() != Tab::kTabClassName)
+ return v;
+
+ // The display order doesn't necessarily match the child list order, so we
+ // walk the display list hit-testing Tabs. Since the selected tab always
+ // renders on top of adjacent tabs, it needs to be hit-tested before any
+ // left-adjacent Tab, so we look ahead for it as we walk.
+ int tab_count = GetTabCount();
+ for (int i = 0; i < tab_count; ++i) {
+ Tab* next_tab = i < (tab_count - 1) ? GetTabAt(i + 1) : NULL;
+ if (next_tab && next_tab->IsSelected() && IsPointInTab(next_tab, point))
+ return next_tab;
+ Tab* tab = GetTabAt(i);
+ if (IsPointInTab(tab, point))
+ return tab;
+ }
+
+ // No need to do any floating view stuff, we don't use them in the TabStrip.
+ return this;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TabStrip, TabStripModelObserver implementation:
+
+void TabStrip::TabInsertedAt(TabContents* contents,
+ int index,
+ bool foreground) {
+ DCHECK(contents);
+ DCHECK(index == TabStripModel::kNoTab || model_->ContainsIndex(index));
+
+ if (active_animation_.get())
+ active_animation_->Stop();
+
+ bool contains_tab = false;
+ Tab* tab = NULL;
+ // First see if this Tab is one that was dragged out of this TabStrip and is
+ // now being dragged back in. In this case, the DraggedTabController actually
+ // has the Tab already constructed and we can just insert it into our list
+ // again.
+ if (IsDragSessionActive()) {
+ tab = drag_controller_->GetDragSourceTabForContents(contents);
+ if (tab) {
+ // If the Tab was detached, it would have been animated closed but not
+ // removed, so we need to reset this property.
+ tab->set_closing(false);
+ tab->ValidateLoadingAnimation(TabRenderer::ANIMATION_NONE);
+ tab->SetVisible(true);
+ }
+
+ // See if we're already in the list. We don't want to add ourselves twice.
+ std::vector<TabData>::const_iterator iter = tab_data_.begin();
+ for (; iter != tab_data_.end() && !contains_tab; ++iter) {
+ if (iter->tab == tab)
+ contains_tab = true;
+ }
+ }
+
+ // Otherwise we need to make a new Tab.
+ if (!tab)
+ tab = new Tab(this);
+
+ // Only insert if we're not already in the list.
+ if (!contains_tab) {
+ if (index == TabStripModel::kNoTab) {
+ TabData d = { tab, gfx::Rect() };
+ tab_data_.push_back(d);
+ tab->UpdateData(contents);
+ } else {
+ TabData d = { tab, gfx::Rect() };
+ tab_data_.insert(tab_data_.begin() + index, d);
+ tab->UpdateData(contents);
+ }
+ }
+
+ // We only add the tab to the child list if it's not already - an invisible
+ // tab maintained by the DraggedTabController will already be parented.
+ if (!tab->GetParent())
+ AddChildView(tab);
+
+ // Don't animate the first tab, it looks weird, and don't animate anything
+ // if the containing window isn't visible yet.
+ if (GetTabCount() > 1 && IsWindowVisible(GetViewContainer()->GetHWND())) {
+ StartInsertTabAnimation(index);
+ } else {
+ Layout();
+ }
+}
+
+void TabStrip::TabDetachedAt(TabContents* contents, int index) {
+ if (CanUpdateDisplay()) {
+ GenerateIdealBounds();
+ StartRemoveTabAnimation(index, contents);
+ // Have to do this _after_ calling StartRemoveTabAnimation, so that any
+ // previous remove is completed fully and index is valid in sync with the
+ // model index.
+ GetTabAt(index)->set_closing(true);
+ }
+}
+
+void TabStrip::TabSelectedAt(TabContents* old_contents,
+ TabContents* new_contents,
+ int index,
+ bool user_gesture) {
+ DCHECK(index >= 0 && index < GetTabCount());
+ if (CanUpdateDisplay()) {
+ // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
+ // a different size to the selected ones.
+ bool tiny_tabs = current_unselected_width_ != current_selected_width_;
+ if (!IsAnimating() && (!resize_layout_scheduled_ || tiny_tabs)) {
+ Layout();
+ } else {
+ SchedulePaint();
+ }
+ }
+}
+
+void TabStrip::TabMoved(TabContents* contents, int from_index, int to_index) {
+ Tab* tab = GetTabAt(from_index);
+ Tab* other_tab = GetTabAt(to_index);
+ tab_data_.erase(tab_data_.begin() + from_index);
+ TabData data = {tab, gfx::Rect()};
+ tab_data_.insert(tab_data_.begin() + to_index, data);
+ GenerateIdealBounds();
+ StartMoveTabAnimation(from_index, to_index);
+}
+
+void TabStrip::TabChangedAt(TabContents* contents, int index) {
+ // Index is in terms of the model. Need to make sure we adjust that index in
+ // case we have an animation going.
+ Tab* tab = GetTabAtAdjustForAnimation(index);
+ tab->UpdateData(contents);
+ tab->UpdateFromModel();
+}
+
+void TabStrip::TabValidateAnimations() {
+ if (model_->TabsAreLoading()) {
+ if (!loading_animation_timer_.IsRunning()) {
+ // Loads are happening, and the timer isn't running, so start it.
+ loading_animation_timer_.Start(
+ TimeDelta::FromMilliseconds(kLoadingAnimationFrameTimeMs), this,
+ &TabStrip::LoadingAnimationCallback);
+ }
+ } else {
+ if (loading_animation_timer_.IsRunning()) {
+ loading_animation_timer_.Stop();
+ // Loads are now complete, update the state if a task was scheduled.
+ LoadingAnimationCallback();
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TabStrip, Tab::Delegate implementation:
+
+bool TabStrip::IsTabSelected(const Tab* tab) const {
+ if (tab->closing())
+ return false;
+
+ int tab_count = GetTabCount();
+ for (int i = 0, index = 0; i < tab_count; ++i, ++index) {
+ Tab* current_tab = GetTabAt(i);
+ if (current_tab->closing())
+ --index;
+ if (current_tab == tab)
+ return index == model_->selected_index();
+ }
+ return false;
+}
+
+void TabStrip::SelectTab(Tab* tab) {
+ int index = GetIndexOfTab(tab);
+ if (model_->ContainsIndex(index))
+ model_->SelectTabContentsAt(index, true);
+}
+
+void TabStrip::CloseTab(Tab* tab) {
+ int tab_index = GetIndexOfTab(tab);
+ if (model_->ContainsIndex(tab_index)) {
+ TabContents* contents = model_->GetTabContentsAt(tab_index);
+ if (contents)
+ UserMetrics::RecordAction(L"CloseTab_Mouse", contents->profile());
+ Tab* last_tab = GetTabAt(GetTabCount() - 1);
+ // Limit the width available to the TabStrip for laying out Tabs, so that
+ // Tabs are not resized until a later time (when the mouse pointer leaves
+ // the TabStrip).
+ available_width_for_tabs_ = GetAvailableWidthForTabs(last_tab);
+ resize_layout_scheduled_ = true;
+ AddMessageLoopObserver();
+ model_->CloseTabContentsAt(tab_index);
+ }
+}
+
+bool TabStrip::IsCommandEnabledForTab(
+ TabStripModel::ContextMenuCommand command_id, const Tab* tab) const {
+ int index = GetIndexOfTab(tab);
+ if (model_->ContainsIndex(index))
+ return model_->IsContextMenuCommandEnabled(index, command_id);
+ return false;
+}
+
+void TabStrip::ExecuteCommandForTab(
+ TabStripModel::ContextMenuCommand command_id, Tab* tab) {
+ int index = GetIndexOfTab(tab);
+ if (model_->ContainsIndex(index))
+ model_->ExecuteContextMenuCommand(index, command_id);
+}
+
+void TabStrip::StartHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id, Tab* tab) {
+ if (command_id == TabStripModel::CommandCloseTabsOpenedBy) {
+ int index = GetIndexOfTab(tab);
+ if (model_->ContainsIndex(index)) {
+ std::vector<int> indices = model_->GetIndexesOpenedBy(index);
+ std::vector<int>::const_iterator iter = indices.begin();
+ for (; iter != indices.end(); ++iter) {
+ int current_index = *iter;
+ DCHECK(current_index >= 0 && current_index < GetTabCount());
+ Tab* current_tab = GetTabAt(current_index);
+ current_tab->StartPulse();
+ }
+ }
+ } else if (command_id == TabStripModel::CommandCloseTabsToRight) {
+ int index = GetIndexOfTab(tab);
+ if (model_->ContainsIndex(index)) {
+ for (int i = index + 1; i < GetTabCount(); ++i) {
+ Tab* current_tab = GetTabAt(i);
+ current_tab->StartPulse();
+ }
+ }
+ } else if (command_id == TabStripModel::CommandCloseOtherTabs) {
+ for (int i = 0; i < GetTabCount(); ++i) {
+ Tab* current_tab = GetTabAt(i);
+ if (current_tab != tab)
+ current_tab->StartPulse();
+ }
+ }
+}
+
+void TabStrip::StopHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id, Tab* tab) {
+ if (command_id == TabStripModel::CommandCloseTabsOpenedBy ||
+ command_id == TabStripModel::CommandCloseTabsToRight ||
+ command_id == TabStripModel::CommandCloseOtherTabs) {
+ // Just tell all Tabs to stop pulsing - it's safe.
+ StopAllHighlighting();
+ }
+}
+
+void TabStrip::StopAllHighlighting() {
+ for (int i = 0; i < GetTabCount(); ++i)
+ GetTabAt(i)->StopPulse();
+}
+
+void TabStrip::MaybeStartDrag(Tab* tab, const ChromeViews::MouseEvent& event) {
+ // Don't accidentally start any drag operations during animations if the
+ // mouse is down... during an animation tabs are being resized automatically,
+ // so the View system can misinterpret this easily if the mouse is down that
+ // the user is dragging.
+ if (IsAnimating() || tab->closing())
+ return;
+ drag_controller_.reset(new DraggedTabController(tab, this));
+ drag_controller_->CaptureDragInfo(gfx::Point(event.GetX(), event.GetY()));
+}
+
+void TabStrip::ContinueDrag(const ChromeViews::MouseEvent& event) {
+ // We can get called even if |MaybeStartDrag| wasn't called in the event of
+ // a TabStrip animation when the mouse button is down. In this case we should
+ // _not_ continue the drag because it can lead to weird bugs.
+ if (drag_controller_.get())
+ drag_controller_->Drag();
+}
+
+void TabStrip::EndDrag(bool canceled) {
+ if (drag_controller_.get())
+ drag_controller_->EndDrag(canceled);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TabStrip, ChromeViews::BaseButton::ButtonListener implementation:
+
+void TabStrip::ButtonPressed(ChromeViews::BaseButton* sender) {
+ if (sender == newtab_button_)
+ model_->AddBlankTab(true);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TabStrip, MessageLoop::Observer implementation:
+
+void TabStrip::WillProcessMessage(const MSG& msg) {
+}
+
+void TabStrip::DidProcessMessage(const MSG& msg) {
+ // We spy on three different Windows messages here to see if the mouse has
+ // moved out of the bounds of the tabstrip, which we use as our cue to kick
+ // of the resize animation. The messages are:
+ //
+ // WM_MOUSEMOVE:
+ // For when the mouse moves from the tabstrip over into the rest of the
+ // browser UI, i.e. within the bounds of the same windows HWND.
+ // WM_MOUSELEAVE:
+ // For when the mouse moves very rapidly from a tab closed in the middle of
+ // the tabstrip (_not_ the end) out of the bounds of the browser's HWND and
+ // over some other HWND.
+ // WM_NCMOUSELEAVE:
+ // For when the mouse moves very rapidly from the end of the tabstrip (when
+ // the last tab is closed and the mouse is left floating over the title
+ // bar). Because the empty area of the tabstrip at the end of the title bar
+ // is registered by the ChromeFrame as part of the "caption" area of the
+ // window (the frame's OnNCHitTest method returns HTCAPTION for this
+ // region), the frame's HWND receives a WM_MOUSEMOVE message immediately,
+ // because as far as it is concerned the mouse has _left_ the client area
+ // of the window (and is now over the non-client area). To be notified
+ // again when the mouse leaves the _non-client_ area, we use the
+ // WM_NCMOUSELEAVE message, which causes us to re-evaluate the cursor
+ // position and correctly resize the tabstrip.
+ //
+ switch (msg.message) {
+ case WM_MOUSEMOVE:
+ case WM_MOUSELEAVE:
+ case WM_NCMOUSELEAVE:
+ if (!IsCursorInTabStripZone()) {
+ // Mouse moved outside the tab slop zone, start a timer to do a resize
+ // layout after a short while...
+ if (resize_layout_factory_.empty()) {
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ resize_layout_factory_.NewRunnableMethod(
+ &TabStrip::ResizeLayoutTabs),
+ kResizeTabsTimeMs);
+ }
+ } else {
+ // Mouse moved quickly out of the tab strip and then into it again, so
+ // cancel the timer so that the strip doesn't move when the mouse moves
+ // back over it.
+ resize_layout_factory_.RevokeAll();
+ }
+ break;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TabStrip, private:
+
+void TabStrip::Init() {
+ model_->AddObserver(this);
+ newtab_button_ = new ChromeViews::Button;
+ newtab_button_->SetListener(this, TabStripModel::kNoTab);
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ SkBitmap* bitmap;
+
+ bitmap = rb.GetBitmapNamed(IDR_NEWTAB_BUTTON);
+ newtab_button_->SetImage(ChromeViews::Button::BS_NORMAL, bitmap);
+ newtab_button_->SetImage(ChromeViews::Button::BS_PUSHED,
+ rb.GetBitmapNamed(IDR_NEWTAB_BUTTON_P));
+ newtab_button_->SetImage(ChromeViews::Button::BS_HOT,
+ rb.GetBitmapNamed(IDR_NEWTAB_BUTTON_H));
+
+ newtab_button_size_.SetSize(bitmap->width(), bitmap->height());
+ actual_newtab_button_size_ = newtab_button_size_;
+
+ newtab_button_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_NEWTAB));
+ AddChildView(newtab_button_);
+
+ if (drop_indicator_width == 0) {
+ // Direction doesn't matter, both images are the same size.
+ SkBitmap* drop_image = GetDropArrowImage(true);
+ drop_indicator_width = drop_image->width();
+ drop_indicator_height = drop_image->height();
+ }
+}
+
+Tab* TabStrip::GetTabAt(int index) const {
+ DCHECK(index >= 0 && index < GetTabCount());
+ return tab_data_.at(index).tab;
+}
+
+Tab* TabStrip::GetTabAtAdjustForAnimation(int index) const {
+ if (active_animation_.get() &&
+ active_animation_->type() == TabAnimation::REMOVE &&
+ index >=
+ static_cast<RemoveTabAnimation*>(active_animation_.get())->index()) {
+ index++;
+ }
+ return GetTabAt(index);
+}
+
+int TabStrip::GetTabCount() const {
+ return static_cast<int>(tab_data_.size());
+}
+
+void TabStrip::GetCurrentTabWidths(double* unselected_width,
+ double* selected_width) const {
+ *unselected_width = current_unselected_width_;
+ *selected_width = current_selected_width_;
+}
+
+void TabStrip::GetDesiredTabWidths(int tab_count,
+ double* unselected_width,
+ double* selected_width) const {
+ const double min_unselected_width = Tab::GetMinimumSize().width();
+ const double min_selected_width = Tab::GetMinimumSelectedSize().width();
+ if (tab_count == 0) {
+ // Return immediately to avoid divide-by-zero below.
+ *unselected_width = min_unselected_width;
+ *selected_width = min_selected_width;
+ return;
+ }
+
+ // Determine how much space we can actually allocate to tabs.
+ int available_width;
+ if (available_width_for_tabs_ < 0) {
+ available_width = GetWidth();
+ available_width -= (kNewTabButtonHOffset + newtab_button_size_.width());
+ } else {
+ // Interesting corner case: if |available_width_for_tabs_| > the result
+ // of the calculation in the conditional arm above, the strip is in
+ // overflow. We can either use the specified width or the true available
+ // width here; the first preserves the consistent "leave the last tab under
+ // the user's mouse so they can close many tabs" behavior at the cost of
+ // prolonging the glitchy appearance of the overflow state, while the second
+ // gets us out of overflow as soon as possible but forces the user to move
+ // their mouse for a few tabs' worth of closing. We choose visual
+ // imperfection over behavioral imperfection and select the first option.
+ available_width = available_width_for_tabs_;
+ }
+
+ // Calculate the desired tab widths by dividing the available space into equal
+ // portions. Don't let tabs get larger than the "standard width" or smaller
+ // than the minimum width for each type, respectively.
+ const int total_offset = kTabHOffset * (tab_count - 1);
+ const double desired_tab_width = std::min((static_cast<double>(
+ available_width - total_offset) / static_cast<double>(tab_count)),
+ static_cast<double>(Tab::GetStandardSize().width()));
+ *unselected_width = std::max(desired_tab_width, min_unselected_width);
+ *selected_width = std::max(desired_tab_width, min_selected_width);
+
+ // When there are multiple tabs, we'll have one selected and some unselected
+ // tabs. If the desired width was between the minimum sizes of these types,
+ // try to shrink the tabs with the smaller minimum. For example, if we have
+ // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If
+ // selected tabs have a minimum width of 4 and unselected tabs have a minimum
+ // width of 1, the above code would set *unselected_width = 2.5,
+ // *selected_width = 4, which results in a total width of 11.5. Instead, we
+ // want to set *unselected_width = 2, *selected_width = 4, for a total width
+ // of 10.
+ if (tab_count > 1) {
+ if ((min_unselected_width < min_selected_width) &&
+ (desired_tab_width < min_selected_width)) {
+ // Unselected width = (total width - selected width) / (num_tabs - 1)
+ *unselected_width = std::max(static_cast<double>(
+ available_width - total_offset - min_selected_width) /
+ static_cast<double>(tab_count - 1), min_unselected_width);
+ } else if ((min_unselected_width > min_selected_width) &&
+ (desired_tab_width < min_unselected_width)) {
+ // Selected width = (total width - (unselected width * (num_tabs - 1)))
+ *selected_width = std::max(available_width - total_offset -
+ (min_unselected_width * (tab_count - 1)), min_selected_width);
+ }
+ }
+}
+
+void TabStrip::ResizeLayoutTabs() {
+ resize_layout_factory_.RevokeAll();
+
+ // It is critically important that this is unhooked here, otherwise we will
+ // keep spying on messages forever.
+ RemoveMessageLoopObserver();
+
+ available_width_for_tabs_ = -1;
+ double unselected, selected;
+ GetDesiredTabWidths(GetTabCount(), &unselected, &selected);
+ Tab* first_tab = GetTabAt(0);
+ int w = Round(first_tab->IsSelected() ? selected : selected);
+
+ // We only want to run the animation if we're not already at the desired
+ // size.
+ if (abs(first_tab->GetWidth() - w) > 1)
+ StartResizeLayoutAnimation();
+}
+
+bool TabStrip::IsCursorInTabStripZone() {
+ CRect bounds;
+ GetLocalBounds(&bounds, true);
+ CPoint tabstrip_topleft = bounds.TopLeft();
+ View::ConvertPointToScreen(this, &tabstrip_topleft);
+ bounds.MoveToXY(tabstrip_topleft);
+ bounds.bottom += kTabStripAnimationVSlop;
+
+ CPoint cursor_point;
+ GetCursorPos(&cursor_point);
+
+ return !!bounds.PtInRect(cursor_point);
+}
+
+void TabStrip::AddMessageLoopObserver() {
+ if (!added_as_message_loop_observer_) {
+ MessageLoopForUI::current()->AddObserver(this);
+ added_as_message_loop_observer_ = true;
+ }
+}
+
+void TabStrip::RemoveMessageLoopObserver() {
+ if (added_as_message_loop_observer_) {
+ MessageLoopForUI::current()->RemoveObserver(this);
+ added_as_message_loop_observer_ = false;
+ }
+}
+
+void TabStrip::LoadingAnimationCallback() {
+ for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) {
+ Tab* current_tab = GetTabAt(i);
+ if (current_tab->closing()) {
+ --index;
+ } else {
+ TabContents* contents = model_->GetTabContentsAt(index);
+ if (!contents || !contents->is_loading()) {
+ current_tab->ValidateLoadingAnimation(Tab::ANIMATION_NONE);
+ } else if (contents->response_started()) {
+ current_tab->ValidateLoadingAnimation(Tab::ANIMATION_WAITING);
+ } else {
+ current_tab->ValidateLoadingAnimation(Tab::ANIMATION_LOADING);
+ }
+ }
+ }
+
+ // Make sure the model delegates updates the animation as well.
+ TabStripModelDelegate* delegate;
+ if (model_ && (delegate = model_->delegate()))
+ delegate->ValidateLoadingAnimations();
+}
+
+gfx::Rect TabStrip::GetDropBounds(int drop_index,
+ bool drop_before,
+ bool* is_beneath) {
+ DCHECK(drop_index != -1);
+ int center_x;
+ if (drop_index < GetTabCount()) {
+ Tab* tab = GetTabAt(drop_index);
+ if (drop_before)
+ center_x = tab->GetX() - (kTabHOffset / 2);
+ else
+ center_x = tab->GetX() + (tab->GetWidth() / 2);
+ } else {
+ Tab* last_tab = GetTabAt(drop_index - 1);
+ center_x = last_tab->GetX() + last_tab->GetWidth() + (kTabHOffset / 2);
+ }
+
+ // Mirror the center point if necessary.
+ center_x = MirroredXCoordinateInsideView(center_x);
+
+ // Determine the screen bounds.
+ CPoint drop_loc(center_x - drop_indicator_width / 2, -drop_indicator_height);
+ ConvertPointToScreen(this, &drop_loc);
+ gfx::Rect drop_bounds(drop_loc.x, drop_loc.y, drop_indicator_width,
+ drop_indicator_height);
+
+ // If the rect doesn't fit on the monitor, push the arrow to the bottom.
+ gfx::Rect monitor_bounds = win_util::GetMonitorBoundsForRect(drop_bounds);
+ *is_beneath = (monitor_bounds.IsEmpty() ||
+ !monitor_bounds.Contains(drop_bounds));
+ if (*is_beneath)
+ drop_bounds.Offset(0, drop_bounds.height() + GetHeight());
+
+ return drop_bounds;
+}
+
+void TabStrip::UpdateDropIndex(const DropTargetEvent& event) {
+ // If the UI layout is right-to-left, we need to mirror the mouse
+ // coordinates since we calculate the drop index based on the
+ // original (and therefore non-mirrored) positions of the tabs.
+ const int x = MirroredXCoordinateInsideView(event.GetX());
+ for (int i = 0; i < GetTabCount(); ++i) {
+ Tab* tab = GetTabAt(i);
+ const int tab_max_x = tab->GetX() + tab->GetWidth();
+ const int hot_width = tab->GetWidth() / 3;
+ if (x < tab_max_x) {
+ if (x < tab->GetX() + hot_width)
+ SetDropIndex(i, true);
+ else if (x >= tab_max_x - hot_width)
+ SetDropIndex(i + 1, true);
+ else
+ SetDropIndex(i, false);
+ return;
+ }
+ }
+
+ // The drop isn't over a tab, add it to the end.
+ SetDropIndex(GetTabCount(), true);
+}
+
+void TabStrip::SetDropIndex(int index, bool drop_before) {
+ if (index == -1) {
+ if (drop_info_.get())
+ drop_info_.reset(NULL);
+ return;
+ }
+
+ if (drop_info_.get() && drop_info_->drop_index == index &&
+ drop_info_->drop_before == drop_before) {
+ return;
+ }
+
+ bool is_beneath;
+ gfx::Rect drop_bounds = GetDropBounds(index, drop_before, &is_beneath);
+
+ if (!drop_info_.get()) {
+ drop_info_.reset(new DropInfo(index, drop_before, !is_beneath));
+ } else {
+ drop_info_->drop_index = index;
+ drop_info_->drop_before = drop_before;
+ if (is_beneath == drop_info_->point_down) {
+ drop_info_->point_down = !is_beneath;
+ drop_info_->arrow_view->SetImage(
+ GetDropArrowImage(drop_info_->point_down));
+ }
+ }
+
+ // Reposition the window. Need to show it too as the window is initially
+ // hidden.
+
+ drop_info_->arrow_window->SetWindowPos(
+ HWND_TOPMOST, drop_bounds.x(), drop_bounds.y(), drop_bounds.width(),
+ drop_bounds.height(), SWP_NOACTIVATE | SWP_SHOWWINDOW);
+}
+
+int TabStrip::GetDropEffect(const ChromeViews::DropTargetEvent& event) {
+ const int source_ops = event.GetSourceOperations();
+ if (source_ops & DragDropTypes::DRAG_COPY)
+ return DragDropTypes::DRAG_COPY;
+ if (source_ops & DragDropTypes::DRAG_LINK)
+ return DragDropTypes::DRAG_LINK;
+ return DragDropTypes::DRAG_MOVE;
+}
+
+// static
+SkBitmap* TabStrip::GetDropArrowImage(bool is_down) {
+ return ResourceBundle::GetSharedInstance().GetBitmapNamed(
+ is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
+}
+
+// TabStrip::DropInfo ----------------------------------------------------------
+
+TabStrip::DropInfo::DropInfo(int drop_index, bool drop_before, bool point_down)
+ : drop_index(drop_index),
+ drop_before(drop_before),
+ point_down(point_down) {
+ arrow_window = new ChromeViews::HWNDViewContainer();
+ arrow_window->set_window_style(WS_POPUP);
+ arrow_window->set_window_ex_style(WS_EX_TOPMOST | WS_EX_NOACTIVATE |
+ WS_EX_LAYERED | WS_EX_TRANSPARENT);
+
+ arrow_view = new ChromeViews::ImageView;
+ arrow_view->SetImage(GetDropArrowImage(point_down));
+
+ arrow_window->Init(
+ NULL,
+ gfx::Rect(0, 0, drop_indicator_width, drop_indicator_height),
+ true);
+ arrow_window->SetContentsView(arrow_view);
+}
+
+TabStrip::DropInfo::~DropInfo() {
+ // Close eventually deletes the window, which deletes arrow_view too.
+ arrow_window->Close();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Called from:
+// - BasicLayout
+// - Tab insertion/removal
+// - Tab reorder
+void TabStrip::GenerateIdealBounds() {
+ int tab_count = GetTabCount();
+ double unselected, selected;
+ GetDesiredTabWidths(tab_count, &unselected, &selected);
+
+ current_unselected_width_ = unselected;
+ current_selected_width_ = selected;
+
+ // NOTE: This currently assumes a tab's height doesn't differ based on
+ // selected state or the number of tabs in the strip!
+ int tab_height = Tab::GetStandardSize().height();
+ double tab_x = 0;
+ for (int i = 0; i < tab_count; ++i) {
+ Tab* tab = GetTabAt(i);
+ double tab_width = unselected;
+ if (tab->IsSelected())
+ tab_width = selected;
+ double end_of_tab = tab_x + tab_width;
+ int rounded_tab_x = Round(tab_x);
+ gfx::Rect state(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
+ tab_height);
+ tab_data_.at(i).ideal_bounds = state;
+ tab_x = end_of_tab + kTabHOffset;
+ }
+}
+
+void TabStrip::LayoutNewTabButton(double last_tab_right,
+ double unselected_width) {
+ int delta = abs(Round(unselected_width) - Tab::GetStandardSize().width());
+ if (delta > 1 && !resize_layout_scheduled_) {
+ // We're shrinking tabs, so we need to anchor the New Tab button to the
+ // right edge of the TabStrip's bounds, rather than the right edge of the
+ // right-most Tab, otherwise it'll bounce when animating.
+ newtab_button_->SetBounds(GetWidth() - newtab_button_size_.width(),
+ kNewTabButtonVOffset,
+ newtab_button_size_.width(),
+ newtab_button_size_.height());
+ } else {
+ newtab_button_->SetBounds(
+ Round(last_tab_right - kTabHOffset) + kNewTabButtonHOffset,
+ kNewTabButtonVOffset, newtab_button_size_.width(),
+ newtab_button_size_.height());
+ }
+}
+
+// Called from:
+// - animation tick
+void TabStrip::AnimationLayout(double unselected_width) {
+ int tab_height = Tab::GetStandardSize().height();
+ double tab_x = 0;
+ for (int i = 0; i < GetTabCount(); ++i) {
+ TabAnimation* animation = active_animation_.get();
+ double tab_width = TabAnimation::GetCurrentTabWidth(this, animation, i);
+ double end_of_tab = tab_x + tab_width;
+ int rounded_tab_x = Round(tab_x);
+ Tab* tab = GetTabAt(i);
+ tab->SetBounds(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
+ tab_height);
+ tab_x = end_of_tab + kTabHOffset;
+ }
+ LayoutNewTabButton(tab_x, unselected_width);
+ SchedulePaint();
+}
+
+void TabStrip::StartResizeLayoutAnimation() {
+ if (active_animation_.get())
+ active_animation_->Stop();
+ active_animation_.reset(new ResizeLayoutAnimation(this));
+ active_animation_->Start();
+}
+
+void TabStrip::StartInsertTabAnimation(int index) {
+ // The TabStrip can now use its entire width to lay out Tabs.
+ available_width_for_tabs_ = -1;
+ if (active_animation_.get())
+ active_animation_->Stop();
+ active_animation_.reset(new InsertTabAnimation(this, index));
+ active_animation_->Start();
+}
+
+void TabStrip::StartRemoveTabAnimation(int index, TabContents* contents) {
+ if (active_animation_.get()) {
+ // Some animations (e.g. MoveTabAnimation) cause there to be a Layout when
+ // they're completed (which includes canceled). Since |tab_data_| is now
+ // inconsistent with TabStripModel, doing this Layout will crash now, so
+ // we ask the MoveTabAnimation to skip its Layout (the state will be
+ // corrected by the RemoveTabAnimation we're about to initiate).
+ active_animation_->set_layout_on_completion(false);
+ active_animation_->Stop();
+ }
+ active_animation_.reset(new RemoveTabAnimation(this, index, contents));
+ active_animation_->Start();
+}
+
+void TabStrip::StartMoveTabAnimation(int from_index, int to_index) {
+ if (active_animation_.get())
+ active_animation_->Stop();
+ active_animation_.reset(new MoveTabAnimation(this, from_index, to_index));
+ active_animation_->Start();
+}
+
+bool TabStrip::CanUpdateDisplay() {
+ // Don't bother laying out/painting when we're closing all tabs.
+ if (model_->closing_all()) {
+ // Make sure any active animation is ended, too.
+ if (active_animation_.get())
+ active_animation_->Stop();
+ return false;
+ }
+ return true;
+}
+
+void TabStrip::FinishAnimation(TabStrip::TabAnimation* animation,
+ bool layout) {
+ active_animation_.reset(NULL);
+ if (layout)
+ Layout();
+}
+
+int TabStrip::GetIndexOfTab(const Tab* tab) const {
+ for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) {
+ Tab* current_tab = GetTabAt(i);
+ if (current_tab->closing()) {
+ --index;
+ } else if (current_tab == tab) {
+ return index;
+ }
+ }
+ return -1;
+}
+
+int TabStrip::GetAvailableWidthForTabs(Tab* last_tab) const {
+ return last_tab->GetX() + last_tab->GetWidth();
+}
+
+bool TabStrip::IsPointInTab(Tab* tab, const CPoint& point_in_tabstrip_coords) {
+ CPoint point_in_tab_coords(point_in_tabstrip_coords);
+ View::ConvertPointToView(this, tab, &point_in_tab_coords);
+ return tab->HitTest(point_in_tab_coords);
+}
+
diff --git a/chrome/browser/views/tabs/tab_strip.h b/chrome/browser/views/tabs/tab_strip.h
new file mode 100644
index 0000000..656dd12
--- /dev/null
+++ b/chrome/browser/views/tabs/tab_strip.h
@@ -0,0 +1,373 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef CHROME_BROWSER_VIEWS_TABS_TAB_STRIP_H__
+#define CHROME_BROWSER_VIEWS_TABS_TAB_STRIP_H__
+
+#include "base/gfx/point.h"
+#include "chrome/browser/tabs/tab_strip_model.h"
+#include "chrome/browser/views/tabs/tab.h"
+#include "chrome/views/button.h"
+#include "chrome/views/hwnd_view_container.h"
+#include "chrome/views/menu.h"
+#include "chrome/views/view.h"
+
+class DraggedTabController;
+class ScopedMouseCloseWidthCalculator;
+class TabStripModel;
+
+namespace ChromeViews {
+class ImageView;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// TabStrip
+//
+// A View that represents the TabStripModel. The TabStrip has the
+// following responsibilities:
+// - It implements the TabStripModelObserver interface, and acts as a
+// container for Tabs, and is also responsible for creating them.
+// - It takes part in Tab Drag & Drop with Tab, TabDragHelper and
+// DraggedTab, focusing on tasks that require reshuffling other tabs
+// in response to dragged tabs.
+//
+///////////////////////////////////////////////////////////////////////////////
+class TabStrip : public ChromeViews::View,
+ public TabStripModelObserver,
+ public Tab::TabDelegate,
+ public ChromeViews::Button::ButtonListener,
+ public MessageLoopForUI::Observer {
+ public:
+ TabStrip(TabStripModel* model);
+ virtual ~TabStrip();
+
+ // Returns the preferred height of this TabStrip. This is based on the
+ // typical height of its constituent tabs.
+ int GetPreferredHeight();
+
+ // Returns true if the associated TabStrip's delegate supports tab moving or
+ // detaching. Used by the Frame to determine if dragging on the Tab
+ // itself should move the window in cases where there's only one
+ // non drag-able Tab.
+ bool HasAvailableDragActions() const;
+
+ // Ask the delegate to show the application menu at the provided point.
+ // The point is in screen coordinate system.
+ void ShowApplicationMenu(const gfx::Point& p);
+
+ // Returns true if the TabStrip can accept input events. This returns false
+ // when the TabStrip is animating to a new state and as such the user should
+ // not be allowed to interact with the TabStrip.
+ bool CanProcessInputEvents() const;
+
+ // Returns true if the specified point (in TabStrip coordinates) is within a
+ // portion of the TabStrip that should be treated as the containing Window's
+ // titlebar for dragging purposes.
+ // TODO(beng): (Cleanup) should be const, but GetViewForPoint isn't, so fie!
+ bool PointIsWithinWindowCaption(const CPoint& point);
+
+ // Return true if this tab strip is compatible with the provided tab strip.
+ // Compatible tab strips can transfer tabs during drag and drop.
+ bool IsCompatibleWith(TabStrip* other);
+
+ // Returns true if Tabs in this TabStrip are currently changing size or
+ // position.
+ bool IsAnimating() const;
+
+ // Accessors for the model and individual Tabs.
+ TabStripModel* model() { return model_; }
+
+ // Returns true if there is an active drag session.
+ bool IsDragSessionActive() const { return drag_controller_.get() != NULL; }
+
+ // Aborts any active drag session. This is called from XP/VistaFrame's
+ // end session handler to make sure there are no drag sessions in flight that
+ // could prevent the frame from being closed right away.
+ void AbortActiveDragSession() { EndDrag(true); }
+
+ // Destroys the active drag controller.
+ void DestroyDragController();
+
+ // Removes the drag source Tab from this TabStrip, and deletes it.
+ void DestroyDraggedSourceTab(Tab* tab);
+
+ // Retrieve the ideal bounds for the Tab at the specified index.
+ gfx::Rect GetIdealBounds(int index);
+
+ // ChromeViews::View overrides:
+ virtual void PaintChildren(ChromeCanvas* canvas);
+ virtual void DidChangeBounds(const CRect& previous, const CRect& current);
+ virtual ChromeViews::View* GetViewByID(int id) const;
+ virtual void Layout();
+ virtual void GetPreferredSize(CSize* preferred_size);
+ // NOTE: the drag and drop methods are invoked from FrameView. This is done to
+ // allow for a drop region that extends outside the bounds of the TabStrip.
+ virtual void OnDragEntered(const ChromeViews::DropTargetEvent& event);
+ virtual int OnDragUpdated(const ChromeViews::DropTargetEvent& event);
+ virtual void OnDragExited();
+ virtual int OnPerformDrop(const ChromeViews::DropTargetEvent& event);
+ virtual bool GetAccessibleRole(VARIANT* role);
+ virtual bool GetAccessibleName(std::wstring* name);
+ virtual void SetAccessibleName(const std::wstring& name);
+ virtual ChromeViews::View* GetViewForPoint(const CPoint& point);
+ virtual ChromeViews::View* GetViewForPoint(const CPoint& point,
+ bool can_create_floating);
+
+ protected:
+ // TabStripModelObserver implementation:
+ virtual void TabInsertedAt(TabContents* contents,
+ int index,
+ bool foreground);
+ virtual void TabDetachedAt(TabContents* contents, int index);
+ virtual void TabSelectedAt(TabContents* old_contents,
+ TabContents* contents,
+ int index,
+ bool user_gesture);
+ virtual void TabMoved(TabContents* contents, int from_index, int to_index);
+ virtual void TabChangedAt(TabContents* contents, int index);
+ virtual void TabValidateAnimations();
+
+ // Tab::Delegate implementation:
+ virtual bool IsTabSelected(const Tab* tab) const;
+ virtual void SelectTab(Tab* tab);
+ virtual void CloseTab(Tab* tab);
+ virtual bool IsCommandEnabledForTab(
+ TabStripModel::ContextMenuCommand command_id, const Tab* tab) const;
+ virtual void ExecuteCommandForTab(
+ TabStripModel::ContextMenuCommand command_id, Tab* tab);
+ virtual void StartHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id, Tab* tab);
+ virtual void StopHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id, Tab* tab);
+ virtual void StopAllHighlighting();
+ virtual void MaybeStartDrag(Tab* tab,
+ const ChromeViews::MouseEvent& event);
+ virtual void ContinueDrag(const ChromeViews::MouseEvent& event);
+ virtual void EndDrag(bool canceled);
+
+ // ChromeViews::Button::ButtonListener implementation:
+ virtual void ButtonPressed(ChromeViews::BaseButton* sender);
+
+ // MessageLoop::Observer implementation:
+ virtual void WillProcessMessage(const MSG& msg);
+ virtual void DidProcessMessage(const MSG& msg);
+
+ private:
+ friend class DraggedTabController;
+ friend class InsertTabAnimation;
+ friend class MoveTabAnimation;
+ friend class RemoveTabAnimation;
+ friend class ResizeLayoutAnimation;
+ friend class SuspendAnimationsTask;
+ friend class TabAnimation;
+
+ TabStrip();
+ void Init();
+
+ // Retrieves the Tab at the specified index. Take care in using this, you may
+ // need to use GetTabAtAdjustForAnimation.
+ Tab* GetTabAt(int index) const;
+
+ // Returns the tab at the specified index. If a remove animation is on going
+ // and the index is >= the index of the tab being removed, the index is
+ // incremented. While a remove operation is on going the indices of the model
+ // do not line up with the indices of the view. This method adjusts the index
+ // accordingly.
+ //
+ // Use this instead of GetTabAt if the index comes from the model.
+ Tab* GetTabAtAdjustForAnimation(int index) const;
+
+ // Gets the number of Tabs in the collection.
+ int GetTabCount() const;
+
+ // -- Tab Resize Layout -----------------------------------------------------
+
+ // Returns the exact (unrounded) current width of each tab.
+ void GetCurrentTabWidths(double* unselected_width,
+ double* selected_width) const;
+
+ // Returns the exact (unrounded) desired width of each tab, based on the
+ // desired strip width and number of tabs. If
+ // |width_of_tabs_for_mouse_close_| is nonnegative we use that value in
+ // calculating the desired strip width; otherwise we use the current width.
+ void GetDesiredTabWidths(int tab_count,
+ double* unselected_width,
+ double* selected_width) const;
+
+ // Perform an animated resize-relayout of the TabStrip immediately.
+ void ResizeLayoutTabs();
+
+ // Returns whether or not the cursor is currently in the "tab strip zone"
+ // which is defined as the region above the TabStrip and a bit below it.
+ // Note: this method cannot be const because |ConvertPointToScreen| is not.
+ // #@*($&(#!!!
+ bool IsCursorInTabStripZone();
+
+ // Ensure that the message loop observer used for event spying is added and
+ // removed appropriately so we can tell when to resize layout the tab strip.
+ void AddMessageLoopObserver();
+ void RemoveMessageLoopObserver();
+
+ // Called to update the frame of the Loading animations.
+ void LoadingAnimationCallback();
+
+ // -- Link Drag & Drop ------------------------------------------------------
+
+ // Returns the bounds to render the drop at, in screen coordinates. Sets
+ // |is_beneath| to indicate whether the arrow is beneath the tab, or above
+ // it.
+ gfx::Rect GetDropBounds(int drop_index, bool drop_before, bool* is_beneath);
+
+ // Updates the location of the drop based on the event.
+ void UpdateDropIndex(const ChromeViews::DropTargetEvent& event);
+
+ // Sets the location of the drop, repainting as necessary.
+ void SetDropIndex(int index, bool drop_before);
+
+ // Returns the drop effect for dropping a URL on the tab strip. This does
+ // not query the data in anyway, it only looks at the source operations.
+ int GetDropEffect(const ChromeViews::DropTargetEvent& event);
+
+ // Returns the image to use for indicating a drop on a tab. If is_down is
+ // true, this returns an arrow pointing down.
+ static SkBitmap* GetDropArrowImage(bool is_down);
+
+ // -- Animations ------------------------------------------------------------
+
+ // Generates the ideal bounds of the TabStrip when all Tabs have finished
+ // animating to their desired position/bounds. This is used by the standard
+ // Layout method and other callers like the DraggedTabController that need
+ // stable representations of Tab positions.
+ void GenerateIdealBounds();
+
+ // Lays out the New Tab button, assuming the right edge of the last Tab on
+ // the TabStrip at |last_tab_right|.
+ void LayoutNewTabButton(double last_tab_right, double unselected_width);
+
+ // A generic Layout method for various classes of TabStrip animations,
+ // including Insert, Remove and Resize Layout cases/
+ void AnimationLayout(double unselected_width);
+
+ // Starts various types of TabStrip animations.
+ void StartResizeLayoutAnimation();
+ void StartInsertTabAnimation(int index);
+ void StartRemoveTabAnimation(int index, TabContents* contents);
+ void StartMoveTabAnimation(int from_index, int to_index);
+
+ // Returns true if detach or select changes in the model should be reflected
+ // in the TabStrip. This returns false if we're closing all tabs in the
+ // TabStrip and so we should prevent updating. This is not const because we
+ // use this as a signal to cancel any active animations.
+ bool CanUpdateDisplay();
+
+ // Notifies the TabStrip that the specified TabAnimation has completed.
+ // Optionally a full Layout will be performed, specified by |layout|.
+ class TabAnimation;
+ void FinishAnimation(TabAnimation* animation, bool layout);
+
+ // Finds the index of the TabContents corresponding to |tab| in our
+ // associated TabStripModel, or -1 if there is none (e.g. the specified |tab|
+ // is being animated closed).
+ int GetIndexOfTab(const Tab* tab) const;
+
+ // Calculates the available width for tabs, assuming a Tab is to be closed.
+ int GetAvailableWidthForTabs(Tab* last_tab) const;
+
+ // Returns true if the specified point in TabStrip coords is within the
+ // hit-test region of the specified Tab.
+ bool IsPointInTab(Tab* tab, const CPoint& point_in_tabstrip_coords);
+
+ // -- Member Variables ------------------------------------------------------
+
+ // Our model.
+ TabStripModel* model_;
+
+ // A factory that is used to construct a delayed callback to the
+ // ResizeLayoutTabsNow method.
+ ScopedRunnableMethodFactory<TabStrip> resize_layout_factory_;
+
+ // True if the TabStrip has already been added as a MessageLoop observer.
+ bool added_as_message_loop_observer_;
+
+ // True if a resize layout animation should be run a short delay after the
+ // mouse exits the TabStrip.
+ // TODO(beng): (Cleanup) this would be better named "needs_resize_layout_".
+ bool resize_layout_scheduled_;
+
+ // The timer used to update frames for the Loading Animation.
+ base::RepeatingTimer<TabStrip> loading_animation_timer_;
+
+ // The "New Tab" button.
+ ChromeViews::Button* newtab_button_;
+ gfx::Size newtab_button_size_;
+ gfx::Size actual_newtab_button_size_;
+
+ // The current widths of various types of tabs. We save these so that, as
+ // users close tabs while we're holding them at the same size, we can lay out
+ // tabs exactly and eliminate the "pixel jitter" we'd get from just leaving
+ // them all at their existing, rounded widths.
+ double current_unselected_width_;
+ double current_selected_width_;
+
+ // If this value is nonnegative, it is used in GetDesiredTabWidths() to
+ // calculate how much space in the tab strip to use for tabs. Most of the
+ // time this will be -1, but while we're handling closing a tab via the mouse,
+ // we'll set this to the edge of the last tab before closing, so that if we
+ // are closing the last tab and need to resize immediately, we'll resize only
+ // back to this width, thus once again placing the last tab under the mouse
+ // cursor.
+ int available_width_for_tabs_;
+
+ // Storage of strings needed for accessibility.
+ std::wstring accessible_name_;
+
+ // Used during a drop session of a url. Tracks the position of the drop as
+ // well as a window used to highlight where the drop occurs.
+ struct DropInfo {
+ DropInfo(int index, bool drop_before, bool paint_down);
+ ~DropInfo();
+
+ // Index of the tab to drop on. If drop_before is true, the drop should
+ // occur between the tab at drop_index - 1 and drop_index.
+ // WARNING: if drop_before is true it is possible this will == tab_count,
+ // which indicates the drop should create a new tab at the end of the tabs.
+ int drop_index;
+ bool drop_before;
+
+ // Direction the arrow should point in. If true, the arrow is displayed
+ // above the tab and points down. If false, the arrow is displayed beneath
+ // the tab and points up.
+ bool point_down;
+
+ // Renders the drop indicator.
+ ChromeViews::HWNDViewContainer* arrow_window;
+ ChromeViews::ImageView* arrow_view;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(DropInfo);
+ };
+
+ // Valid for the lifetime of a drag over us.
+ scoped_ptr<DropInfo> drop_info_;
+
+ // The controller for a drag initiated from a Tab. Valid for the lifetime of
+ // the drag session.
+ scoped_ptr<DraggedTabController> drag_controller_;
+
+ // The Tabs we contain, and their last generated "good" bounds.
+ struct TabData {
+ Tab* tab;
+ gfx::Rect ideal_bounds;
+ };
+ std::vector<TabData> tab_data_;
+
+ // The currently running animation.
+ scoped_ptr<TabAnimation> active_animation_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TabStrip);
+};
+
+#endif // CHROME_BROWSER_VIEWS_TABS_TAB_STRIP_H__
+