From 3146282c2250c51569d971234fbc9756b4d7a2d4 Mon Sep 17 00:00:00 2001 From: "beng@google.com" Date: Fri, 12 Sep 2008 22:44:06 +0000 Subject: 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 --- chrome/browser/browser.vcproj | 50 +- chrome/browser/tabs/dragged_tab_controller.cc | 798 ---------- chrome/browser/tabs/dragged_tab_controller.h | 286 ---- chrome/browser/tabs/dragged_tab_view.cc | 244 ---- chrome/browser/tabs/dragged_tab_view.h | 119 -- chrome/browser/tabs/hwnd_photobooth.cc | 159 -- chrome/browser/tabs/hwnd_photobooth.h | 62 - chrome/browser/tabs/tab.cc | 241 ---- chrome/browser/tabs/tab.h | 125 -- chrome/browser/tabs/tab_dragging_test.cc | 507 ------- chrome/browser/tabs/tab_renderer.cc | 691 --------- chrome/browser/tabs/tab_renderer.h | 174 --- chrome/browser/tabs/tab_strip.cc | 1524 -------------------- chrome/browser/tabs/tab_strip.h | 373 ----- chrome/browser/views/browser_views.vcproj | 52 + .../browser/views/tabs/dragged_tab_controller.cc | 798 ++++++++++ chrome/browser/views/tabs/dragged_tab_controller.h | 286 ++++ chrome/browser/views/tabs/dragged_tab_view.cc | 244 ++++ chrome/browser/views/tabs/dragged_tab_view.h | 119 ++ chrome/browser/views/tabs/hwnd_photobooth.cc | 159 ++ chrome/browser/views/tabs/hwnd_photobooth.h | 62 + chrome/browser/views/tabs/tab.cc | 241 ++++ chrome/browser/views/tabs/tab.h | 125 ++ chrome/browser/views/tabs/tab_dragging_test.cc | 507 +++++++ chrome/browser/views/tabs/tab_renderer.cc | 691 +++++++++ chrome/browser/views/tabs/tab_renderer.h | 174 +++ chrome/browser/views/tabs/tab_strip.cc | 1524 ++++++++++++++++++++ chrome/browser/views/tabs/tab_strip.h | 373 +++++ chrome/test/interactive_ui/interactive_ui.vcproj | 2 +- 29 files changed, 5357 insertions(+), 5353 deletions(-) delete mode 100644 chrome/browser/tabs/dragged_tab_controller.cc delete mode 100644 chrome/browser/tabs/dragged_tab_controller.h delete mode 100644 chrome/browser/tabs/dragged_tab_view.cc delete mode 100644 chrome/browser/tabs/dragged_tab_view.h delete mode 100644 chrome/browser/tabs/hwnd_photobooth.cc delete mode 100644 chrome/browser/tabs/hwnd_photobooth.h delete mode 100644 chrome/browser/tabs/tab.cc delete mode 100644 chrome/browser/tabs/tab.h delete mode 100644 chrome/browser/tabs/tab_dragging_test.cc delete mode 100644 chrome/browser/tabs/tab_renderer.cc delete mode 100644 chrome/browser/tabs/tab_renderer.h delete mode 100644 chrome/browser/tabs/tab_strip.cc delete mode 100644 chrome/browser/tabs/tab_strip.h create mode 100644 chrome/browser/views/tabs/dragged_tab_controller.cc create mode 100644 chrome/browser/views/tabs/dragged_tab_controller.h create mode 100644 chrome/browser/views/tabs/dragged_tab_view.cc create mode 100644 chrome/browser/views/tabs/dragged_tab_view.h create mode 100644 chrome/browser/views/tabs/hwnd_photobooth.cc create mode 100644 chrome/browser/views/tabs/hwnd_photobooth.h create mode 100644 chrome/browser/views/tabs/tab.cc create mode 100644 chrome/browser/views/tabs/tab.h create mode 100644 chrome/browser/views/tabs/tab_dragging_test.cc create mode 100644 chrome/browser/views/tabs/tab_renderer.cc create mode 100644 chrome/browser/views/tabs/tab_renderer.h create mode 100644 chrome/browser/views/tabs/tab_strip.cc create mode 100644 chrome/browser/views/tabs/tab_strip.h diff --git a/chrome/browser/browser.vcproj b/chrome/browser/browser.vcproj index c3ad1ba..f560330 100644 --- a/chrome/browser/browser.vcproj +++ b/chrome/browser/browser.vcproj @@ -1683,57 +1683,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/chrome/browser/tabs/dragged_tab_controller.cc b/chrome/browser/tabs/dragged_tab_controller.cc deleted file mode 100644 index 80cbe65..0000000 --- a/chrome/browser/tabs/dragged_tab_controller.cc +++ /dev/null @@ -1,798 +0,0 @@ -// 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/tabs/dragged_tab_controller.h" - -#include - -#include "chrome/browser/browser_window.h" -#include "chrome/browser/frame_util.h" -#include "chrome/browser/tab_contents.h" -#include "chrome/browser/tabs/dragged_tab_view.h" -#include "chrome/browser/tabs/hwnd_photobooth.h" -#include "chrome/browser/tabs/tab.h" -#include "chrome/browser/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(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(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(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(dragged_contents_)); - } - dragged_contents_ = new_contents; - if (dragged_contents_) { - NotificationService::current()->AddObserver(this, - NOTIFY_TAB_CONTENTS_DESTROYED, - Source(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(x_offset), 2) + - pow(static_cast(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(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(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/tabs/dragged_tab_controller.h b/chrome/browser/tabs/dragged_tab_controller.h deleted file mode 100644 index 031a6ed..0000000 --- a/chrome/browser/tabs/dragged_tab_controller.h +++ /dev/null @@ -1,286 +0,0 @@ -// 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_TABS_DRAGGED_TAB_CONTROLLER_H_ -#define CHROME_BROWSER_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/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 view_; - - // The photo-booth the TabContents sits in when the Tab is detached, to - // obtain screen shots. - scoped_ptr 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_TABS_DRAGGED_TAB_CONTROLLER_H_ - diff --git a/chrome/browser/tabs/dragged_tab_view.cc b/chrome/browser/tabs/dragged_tab_view.cc deleted file mode 100644 index 0878476..0000000 --- a/chrome/browser/tabs/dragged_tab_view.cc +++ /dev/null @@ -1,244 +0,0 @@ -// 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/tabs/dragged_tab_view.h" - -#include "chrome/common/gfx/chrome_canvas.h" -#include "chrome/browser/tab_contents.h" -#include "chrome/browser/tabs/hwnd_photobooth.h" -#include "chrome/browser/tabs/tab_renderer.h" -#include "chrome/browser/tabs/tab_strip_model.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(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( - 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(value * kScalingFactor); -} - diff --git a/chrome/browser/tabs/dragged_tab_view.h b/chrome/browser/tabs/dragged_tab_view.h deleted file mode 100644 index 8419342..0000000 --- a/chrome/browser/tabs/dragged_tab_view.h +++ /dev/null @@ -1,119 +0,0 @@ -// 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_TABS_DRAGGED_TAB_VIEW_H_ -#define CHROME_BROWSER_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 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 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_TABS_DRAGGED_TAB_VIEW_H_ - diff --git a/chrome/browser/tabs/hwnd_photobooth.cc b/chrome/browser/tabs/hwnd_photobooth.cc deleted file mode 100644 index 7ae6c69..0000000 --- a/chrome/browser/tabs/hwnd_photobooth.cc +++ /dev/null @@ -1,159 +0,0 @@ -// 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/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(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(&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/tabs/hwnd_photobooth.h b/chrome/browser/tabs/hwnd_photobooth.h deleted file mode 100644 index 608236b..0000000 --- a/chrome/browser/tabs/hwnd_photobooth.h +++ /dev/null @@ -1,62 +0,0 @@ -// 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_HWND_PHOTOBOOTH_H__ -#define CHROME_BROWSER_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_HWND_PHOTOBOOTH_H__ - diff --git a/chrome/browser/tabs/tab.cc b/chrome/browser/tabs/tab.cc deleted file mode 100644 index f855701..0000000 --- a/chrome/browser/tabs/tab.cc +++ /dev/null @@ -1,241 +0,0 @@ -// 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/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(id), - tab_); - } - - virtual void ExecuteCommand(int id) { - tab_->delegate()->ExecuteCommandForTab( - static_cast(id), - tab_); - } - - virtual void SelectionChanged(ChromeViews::MenuItemView* menu) { - TabStripModel::ContextMenuCommand command = - static_cast(menu->GetCommand()); - tab_->delegate()->StopHighlightTabsForCommand(last_command_, tab_); - last_command_ = command; - tab_->delegate()->StartHighlightTabsForCommand(command, tab_); - } - - private: - // The context menu. - scoped_ptr 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/tabs/tab.h b/chrome/browser/tabs/tab.h deleted file mode 100644 index 10346ef..0000000 --- a/chrome/browser/tabs/tab.h +++ /dev/null @@ -1,125 +0,0 @@ -// 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_TABS_TAB_H_ -#define CHROME_BROWSER_TABS_TAB_H_ - -#include "chrome/browser/tabs/tab_renderer.h" -#include "chrome/browser/tabs/tab_strip_model.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_TABS_TAB_H_ - diff --git a/chrome/browser/tabs/tab_dragging_test.cc b/chrome/browser/tabs/tab_dragging_test.cc deleted file mode 100644 index 631a0f0..0000000 --- a/chrome/browser/tabs/tab_dragging_test.cc +++ /dev/null @@ -1,507 +0,0 @@ -// 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 browser(automation()->GetBrowserWindow(0)); - ASSERT_TRUE(browser.get()); - scoped_ptr 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 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 tab2(browser->GetTab(1)); - ASSERT_TRUE(tab2.get()); - - // Add Tab_3. - GURL tab3_url("about:plugins"); - ASSERT_TRUE(browser->AppendTab(tab3_url)); - scoped_ptr 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 browser(automation()->GetBrowserWindow(0)); - ASSERT_TRUE(browser.get()); - scoped_ptr 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 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 tab2(browser->GetTab(1)); - ASSERT_TRUE(tab2.get()); - - // Add Tab_3. - GURL tab3_url("about:plugins"); - ASSERT_TRUE(browser->AppendTab(tab3_url)); - scoped_ptr 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 browser(automation()->GetBrowserWindow(0)); - ASSERT_TRUE(browser.get()); - scoped_ptr 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 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 tab2(browser->GetTab(1)); - ASSERT_TRUE(tab2.get()); - - // Add Tab_3. - GURL tab3_url("about:plugins"); - ASSERT_TRUE(browser->AppendTab(tab3_url)); - scoped_ptr 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 browser(automation()->GetBrowserWindow(0)); - ASSERT_TRUE(browser.get()); - scoped_ptr 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 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 tab2(browser->GetTab(1)); - ASSERT_TRUE(tab2.get()); - - // Add Tab_3. - GURL tab3_url("about:plugins"); - ASSERT_TRUE(browser->AppendTab(tab3_url)); - scoped_ptr 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 browser2(automation()->GetBrowserWindow(1)); - ASSERT_TRUE(browser2.get()); - scoped_ptr 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 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/tabs/tab_renderer.cc b/chrome/browser/tabs/tab_renderer.cc deleted file mode 100644 index 3437900..0000000 --- a/chrome/browser/tabs/tab_renderer.cc +++ /dev/null @@ -1,691 +0,0 @@ -// 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 - -#include "chrome/browser/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(floor(kHidingOffset * 2.0 * state))); - } else { - target_->DisplayCrashedFavIcon(); - target_->SetFavIconHidingOffset( - static_cast( - 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::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/tabs/tab_renderer.h b/chrome/browser/tabs/tab_renderer.h deleted file mode 100644 index 25c2de8..0000000 --- a/chrome/browser/tabs/tab_renderer.h +++ /dev/null @@ -1,174 +0,0 @@ -// 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_TABS_TAB_RENDERER_H__ -#define CHROME_BROWSER_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 hover_animation_; - - // Pulse animation. - scoped_ptr 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_TABS_TAB_RENDERER_H__ - diff --git a/chrome/browser/tabs/tab_strip.cc b/chrome/browser/tabs/tab_strip.cc deleted file mode 100644 index 57a8e32..0000000 --- a/chrome/browser/tabs/tab_strip.cc +++ /dev/null @@ -1,1524 +0,0 @@ -// 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/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/dragged_tab_controller.h" -#include "chrome/browser/tabs/tab.h" -#include "chrome/browser/tabs/tab_strip_model.h" -#include "chrome/browser/user_metrics.h" -#include "chrome/browser/view_ids.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(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(TabRenderer::GetStandardSize().width()); - if (start_tab_count < end_tab_count && - start_unselected_width_ < standard_tab_width) { - double minimum_tab_width = - static_cast(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::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(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::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 indices = model_->GetIndexesOpenedBy(index); - std::vector::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(active_animation_.get())->index()) { - index++; - } - return GetTabAt(index); -} - -int TabStrip::GetTabCount() const { - return static_cast(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( - available_width - total_offset) / static_cast(tab_count)), - static_cast(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( - available_width - total_offset - min_selected_width) / - static_cast(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/tabs/tab_strip.h b/chrome/browser/tabs/tab_strip.h deleted file mode 100644 index 11f6f64..0000000 --- a/chrome/browser/tabs/tab_strip.h +++ /dev/null @@ -1,373 +0,0 @@ -// 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_TABS_TAB_STRIP_H__ -#define CHROME_BROWSER_TABS_TAB_STRIP_H__ - -#include "base/gfx/point.h" -#include "chrome/browser/tabs/tab.h" -#include "chrome/browser/tabs/tab_strip_model.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 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 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 drop_info_; - - // The controller for a drag initiated from a Tab. Valid for the lifetime of - // the drag session. - scoped_ptr drag_controller_; - - // The Tabs we contain, and their last generated "good" bounds. - struct TabData { - Tab* tab; - gfx::Rect ideal_bounds; - }; - std::vector tab_data_; - - // The currently running animation. - scoped_ptr active_animation_; - - DISALLOW_EVIL_CONSTRUCTORS(TabStrip); -}; - -#endif // CHROME_BROWSER_TABS_TAB_STRIP_H__ - diff --git a/chrome/browser/views/browser_views.vcproj b/chrome/browser/views/browser_views.vcproj index 1b4f0d0..d7072d7 100644 --- a/chrome/browser/views/browser_views.vcproj +++ b/chrome/browser/views/browser_views.vcproj @@ -329,6 +329,58 @@ > + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + +#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(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(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(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(dragged_contents_)); + } + dragged_contents_ = new_contents; + if (dragged_contents_) { + NotificationService::current()->AddObserver(this, + NOTIFY_TAB_CONTENTS_DESTROYED, + Source(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(x_offset), 2) + + pow(static_cast(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(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(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 view_; + + // The photo-booth the TabContents sits in when the Tab is detached, to + // obtain screen shots. + scoped_ptr 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(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( + 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(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 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 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(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(&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(id), + tab_); + } + + virtual void ExecuteCommand(int id) { + tab_->delegate()->ExecuteCommandForTab( + static_cast(id), + tab_); + } + + virtual void SelectionChanged(ChromeViews::MenuItemView* menu) { + TabStripModel::ContextMenuCommand command = + static_cast(menu->GetCommand()); + tab_->delegate()->StopHighlightTabsForCommand(last_command_, tab_); + last_command_ = command; + tab_->delegate()->StartHighlightTabsForCommand(command, tab_); + } + + private: + // The context menu. + scoped_ptr 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 browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + scoped_ptr 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 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 tab2(browser->GetTab(1)); + ASSERT_TRUE(tab2.get()); + + // Add Tab_3. + GURL tab3_url("about:plugins"); + ASSERT_TRUE(browser->AppendTab(tab3_url)); + scoped_ptr 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 browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + scoped_ptr 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 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 tab2(browser->GetTab(1)); + ASSERT_TRUE(tab2.get()); + + // Add Tab_3. + GURL tab3_url("about:plugins"); + ASSERT_TRUE(browser->AppendTab(tab3_url)); + scoped_ptr 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 browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + scoped_ptr 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 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 tab2(browser->GetTab(1)); + ASSERT_TRUE(tab2.get()); + + // Add Tab_3. + GURL tab3_url("about:plugins"); + ASSERT_TRUE(browser->AppendTab(tab3_url)); + scoped_ptr 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 browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + scoped_ptr 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 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 tab2(browser->GetTab(1)); + ASSERT_TRUE(tab2.get()); + + // Add Tab_3. + GURL tab3_url("about:plugins"); + ASSERT_TRUE(browser->AppendTab(tab3_url)); + scoped_ptr 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 browser2(automation()->GetBrowserWindow(1)); + ASSERT_TRUE(browser2.get()); + scoped_ptr 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 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 + +#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(floor(kHidingOffset * 2.0 * state))); + } else { + target_->DisplayCrashedFavIcon(); + target_->SetFavIconHidingOffset( + static_cast( + 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::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 hover_animation_; + + // Pulse animation. + scoped_ptr 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(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(TabRenderer::GetStandardSize().width()); + if (start_tab_count < end_tab_count && + start_unselected_width_ < standard_tab_width) { + double minimum_tab_width = + static_cast(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::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(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::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 indices = model_->GetIndexesOpenedBy(index); + std::vector::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(active_animation_.get())->index()) { + index++; + } + return GetTabAt(index); +} + +int TabStrip::GetTabCount() const { + return static_cast(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( + available_width - total_offset) / static_cast(tab_count)), + static_cast(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( + available_width - total_offset - min_selected_width) / + static_cast(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 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 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 drop_info_; + + // The controller for a drag initiated from a Tab. Valid for the lifetime of + // the drag session. + scoped_ptr drag_controller_; + + // The Tabs we contain, and their last generated "good" bounds. + struct TabData { + Tab* tab; + gfx::Rect ideal_bounds; + }; + std::vector tab_data_; + + // The currently running animation. + scoped_ptr active_animation_; + + DISALLOW_EVIL_CONSTRUCTORS(TabStrip); +}; + +#endif // CHROME_BROWSER_VIEWS_TABS_TAB_STRIP_H__ + diff --git a/chrome/test/interactive_ui/interactive_ui.vcproj b/chrome/test/interactive_ui/interactive_ui.vcproj index 5449a05..3405e29 100644 --- a/chrome/test/interactive_ui/interactive_ui.vcproj +++ b/chrome/test/interactive_ui/interactive_ui.vcproj @@ -227,7 +227,7 @@ Name="TestTabDragging" > -- cgit v1.1