普通文本  |  564行  |  16.45 KB

// Copyright (c) 2011 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/ui/views/tabs/base_tab_strip.h"

#include "base/logging.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/tabs/dragged_tab_controller.h"
#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
#include "views/widget/root_view.h"
#include "views/window/window.h"

#if defined(OS_WIN)
#include "views/widget/widget_win.h"
#endif

namespace {

// Animation delegate used when a dragged tab is released. When done sets the
// dragging state to false.
class ResetDraggingStateDelegate
    : public views::BoundsAnimator::OwnedAnimationDelegate {
 public:
  explicit ResetDraggingStateDelegate(BaseTab* tab) : tab_(tab) {
  }

  virtual void AnimationEnded(const ui::Animation* animation) {
    tab_->set_dragging(false);
  }

  virtual void AnimationCanceled(const ui::Animation* animation) {
    tab_->set_dragging(false);
  }

 private:
  BaseTab* tab_;

  DISALLOW_COPY_AND_ASSIGN(ResetDraggingStateDelegate);
};

}  // namespace

// AnimationDelegate used when removing a tab. Does the necessary cleanup when
// done.
class BaseTabStrip::RemoveTabDelegate
    : public views::BoundsAnimator::OwnedAnimationDelegate {
 public:
  RemoveTabDelegate(BaseTabStrip* tab_strip, BaseTab* tab)
      : tabstrip_(tab_strip),
        tab_(tab) {
  }

  virtual void AnimationEnded(const ui::Animation* animation) {
    CompleteRemove();
  }

  virtual void AnimationCanceled(const ui::Animation* animation) {
    // We can be canceled for two interesting reasons:
    // . The tab we reference was dragged back into the tab strip. In this case
    //   we don't want to remove the tab (closing is false).
    // . The drag was completed before the animation completed
    //   (DestroyDraggedSourceTab). In this case we need to remove the tab
    //   (closing is true).
    if (tab_->closing())
      CompleteRemove();
  }

 private:
  void CompleteRemove() {
    if (!tab_->closing()) {
      // The tab was added back yet we weren't canceled. This shouldn't happen.
      NOTREACHED();
      return;
    }
    tabstrip_->RemoveAndDeleteTab(tab_);
    HighlightCloseButton();
  }

  // When the animation completes, we send the Container 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_->IsDragSessionActive() ||
        !tabstrip_->ShouldHighlightCloseButtonAfterRemove()) {
      // This function is not required (and indeed may crash!) for removes
      // spawned by non-mouse closes and drag-detaches.
      return;
    }

#if defined(OS_WIN)
    views::Widget* widget = tabstrip_->GetWidget();
    // This can be null during shutdown. See http://crbug.com/42737.
    if (!widget)
      return;
    // Force the close button (that slides under the mouse) to highlight by
    // saying the mouse just moved, but sending the same coordinates.
    DWORD pos = GetMessagePos();
    POINT cursor_point = {GET_X_LPARAM(pos), GET_Y_LPARAM(pos)};
    MapWindowPoints(NULL, widget->GetNativeView(), &cursor_point, 1);

    static_cast<views::WidgetWin*>(widget)->ResetLastMouseMoveFlag();
    // Return to message loop - otherwise we may disrupt some operation that's
    // in progress.
    SendMessage(widget->GetNativeView(), WM_MOUSEMOVE, 0,
                MAKELPARAM(cursor_point.x, cursor_point.y));
#else
    NOTIMPLEMENTED();
#endif
  }

  BaseTabStrip* tabstrip_;
  BaseTab* tab_;

  DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate);
};

BaseTabStrip::BaseTabStrip(TabStripController* controller, Type type)
    : controller_(controller),
      type_(type),
      attaching_dragged_tab_(false),
      ALLOW_THIS_IN_INITIALIZER_LIST(bounds_animator_(this)) {
}

BaseTabStrip::~BaseTabStrip() {
}

void BaseTabStrip::AddTabAt(int model_index, const TabRendererData& data) {
  BaseTab* tab = CreateTab();
  tab->SetData(data);

  TabData d = { tab, gfx::Rect() };
  tab_data_.insert(tab_data_.begin() + ModelIndexToTabIndex(model_index), d);

  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 (tab_count() > 1 && GetWindow() && GetWindow()->IsVisible())
    StartInsertTabAnimation(model_index);
  else
    DoLayout();
}

void BaseTabStrip::MoveTab(int from_model_index, int to_model_index) {
  int from_tab_data_index = ModelIndexToTabIndex(from_model_index);
  BaseTab* tab = tab_data_[from_tab_data_index].tab;
  tab_data_.erase(tab_data_.begin() + from_tab_data_index);

  TabData data = {tab, gfx::Rect()};
  int to_tab_data_index = ModelIndexToTabIndex(to_model_index);
  tab_data_.insert(tab_data_.begin() + to_tab_data_index, data);

  StartMoveTabAnimation();
}

void BaseTabStrip::SetTabData(int model_index, const TabRendererData& data) {
  BaseTab* tab = GetBaseTabAtModelIndex(model_index);
  bool mini_state_changed = tab->data().mini != data.mini;
  tab->SetData(data);

  if (mini_state_changed) {
    if (GetWindow() && GetWindow()->IsVisible())
      StartMiniTabAnimation();
    else
      DoLayout();
  }
}

BaseTab* BaseTabStrip::GetBaseTabAtModelIndex(int model_index) const {
  return base_tab_at_tab_index(ModelIndexToTabIndex(model_index));
}

int BaseTabStrip::GetModelIndexOfBaseTab(const BaseTab* tab) const {
  for (int i = 0, model_index = 0; i < tab_count(); ++i) {
    BaseTab* current_tab = base_tab_at_tab_index(i);
    if (!current_tab->closing()) {
      if (current_tab == tab)
        return model_index;
      model_index++;
    } else if (current_tab == tab) {
      return -1;
    }
  }
  return -1;
}

int BaseTabStrip::GetModelCount() const {
  return controller_->GetCount();
}

bool BaseTabStrip::IsValidModelIndex(int model_index) const {
  return controller_->IsValidIndex(model_index);
}

int BaseTabStrip::ModelIndexToTabIndex(int model_index) const {
  int current_model_index = 0;
  for (int i = 0; i < tab_count(); ++i) {
    if (!base_tab_at_tab_index(i)->closing()) {
      if (current_model_index == model_index)
        return i;
      current_model_index++;
    }
  }
  return static_cast<int>(tab_data_.size());
}

bool BaseTabStrip::IsDragSessionActive() const {
  return drag_controller_.get() != NULL;
}

bool BaseTabStrip::IsActiveDropTarget() const {
  for (int i = 0; i < tab_count(); ++i) {
    BaseTab* tab = base_tab_at_tab_index(i);
    if (tab->dragging())
      return true;
  }
  return false;
}

bool BaseTabStrip::IsTabStripEditable() const {
  return !IsDragSessionActive() && !IsActiveDropTarget();
}

bool BaseTabStrip::IsTabStripCloseable() const {
  return !IsDragSessionActive();
}

void BaseTabStrip::UpdateLoadingAnimations() {
  controller_->UpdateLoadingAnimations();
}

void BaseTabStrip::SelectTab(BaseTab* tab) {
  int model_index = GetModelIndexOfBaseTab(tab);
  if (IsValidModelIndex(model_index))
    controller_->SelectTab(model_index);
}

void BaseTabStrip::ExtendSelectionTo(BaseTab* tab) {
  int model_index = GetModelIndexOfBaseTab(tab);
  if (IsValidModelIndex(model_index))
    controller_->ExtendSelectionTo(model_index);
}

void BaseTabStrip::ToggleSelected(BaseTab* tab) {
  int model_index = GetModelIndexOfBaseTab(tab);
  if (IsValidModelIndex(model_index))
    controller_->ToggleSelected(model_index);
}

void BaseTabStrip::AddSelectionFromAnchorTo(BaseTab* tab) {
  int model_index = GetModelIndexOfBaseTab(tab);
  if (IsValidModelIndex(model_index))
    controller_->AddSelectionFromAnchorTo(model_index);
}

void BaseTabStrip::CloseTab(BaseTab* tab) {
  // Find the closest model index. We do this so that the user can rapdily close
  // tabs and have the close click close the next tab.
  int model_index = 0;
  for (int i = 0; i < tab_count(); ++i) {
    BaseTab* current_tab = base_tab_at_tab_index(i);
    if (current_tab == tab)
      break;
    if (!current_tab->closing())
      model_index++;
  }

  if (IsValidModelIndex(model_index))
    controller_->CloseTab(model_index);
}

void BaseTabStrip::ShowContextMenuForTab(BaseTab* tab, const gfx::Point& p) {
  controller_->ShowContextMenuForTab(tab, p);
}

bool BaseTabStrip::IsActiveTab(const BaseTab* tab) const {
  int model_index = GetModelIndexOfBaseTab(tab);
  return IsValidModelIndex(model_index) &&
      controller_->IsActiveTab(model_index);
}

bool BaseTabStrip::IsTabSelected(const BaseTab* tab) const {
  int model_index = GetModelIndexOfBaseTab(tab);
  return IsValidModelIndex(model_index) &&
      controller_->IsTabSelected(model_index);
}

bool BaseTabStrip::IsTabPinned(const BaseTab* tab) const {
  if (tab->closing())
    return false;

  int model_index = GetModelIndexOfBaseTab(tab);
  return IsValidModelIndex(model_index) &&
      controller_->IsTabPinned(model_index);
}

bool BaseTabStrip::IsTabCloseable(const BaseTab* tab) const {
  int model_index = GetModelIndexOfBaseTab(tab);
  return !IsValidModelIndex(model_index) ||
      controller_->IsTabCloseable(model_index);
}

void BaseTabStrip::MaybeStartDrag(BaseTab* tab,
                                  const views::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() ||
      controller_->HasAvailableDragActions() == 0) {
    return;
  }
  int model_index = GetModelIndexOfBaseTab(tab);
  if (!IsValidModelIndex(model_index)) {
    CHECK(false);
    return;
  }
  drag_controller_.reset(new DraggedTabController());
  std::vector<BaseTab*> tabs;
  int size_to_selected = 0;
  int x = tab->GetMirroredXInView(event.x());
  int y = event.y();
  // Build the set of selected tabs to drag and calculate the offset from the
  // first selected tab.
  for (int i = 0; i < tab_count(); ++i) {
    BaseTab* other_tab = base_tab_at_tab_index(i);
    if (IsTabSelected(other_tab) && !other_tab->closing()) {
      tabs.push_back(other_tab);
      if (other_tab == tab) {
        size_to_selected = GetSizeNeededForTabs(tabs);
        if (type() == HORIZONTAL_TAB_STRIP)
          x = size_to_selected - tab->width() + x;
        else
          y = size_to_selected - tab->height() + y;
      }
    }
  }
  DCHECK(!tabs.empty());
  DCHECK(std::find(tabs.begin(), tabs.end(), tab) != tabs.end());
  drag_controller_->Init(this, tab, tabs, gfx::Point(x, y),
                         tab->GetMirroredXInView(event.x()));
}

void BaseTabStrip::ContinueDrag(const views::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()) {
    bool started_drag = drag_controller_->started_drag();
    drag_controller_->Drag();
    if (drag_controller_->started_drag() && !started_drag) {
      // The drag just started. Redirect mouse events to us to that the tab that
      // originated the drag can be safely deleted.
      GetRootView()->SetMouseHandler(this);
    }
  }
}

bool BaseTabStrip::EndDrag(bool canceled) {
  if (!drag_controller_.get())
    return false;
  bool started_drag = drag_controller_->started_drag();
  drag_controller_->EndDrag(canceled);
  return started_drag;
}

BaseTab* BaseTabStrip::GetTabAt(BaseTab* tab,
                                const gfx::Point& tab_in_tab_coordinates) {
  gfx::Point local_point = tab_in_tab_coordinates;
  ConvertPointToView(tab, this, &local_point);
  return GetTabAtLocal(local_point);
}

void BaseTabStrip::Layout() {
  // Only do a layout if our size changed.
  if (last_layout_size_ == size())
    return;
  DoLayout();
}

bool BaseTabStrip::OnMouseDragged(const views::MouseEvent&  event) {
  if (drag_controller_.get())
    drag_controller_->Drag();
  return true;
}

void BaseTabStrip::OnMouseReleased(const views::MouseEvent& event) {
  EndDrag(false);
}

void BaseTabStrip::OnMouseCaptureLost() {
  EndDrag(true);
}

void BaseTabStrip::StartMoveTabAnimation() {
  PrepareForAnimation();
  GenerateIdealBounds();
  AnimateToIdealBounds();
}

void BaseTabStrip::StartRemoveTabAnimation(int model_index) {
  PrepareForAnimation();

  // Mark the tab as closing.
  BaseTab* tab = GetBaseTabAtModelIndex(model_index);
  tab->set_closing(true);

  // Start an animation for the tabs.
  GenerateIdealBounds();
  AnimateToIdealBounds();

  // Animate the tab being closed to 0x0.
  gfx::Rect tab_bounds = tab->bounds();
  if (type() == HORIZONTAL_TAB_STRIP)
    tab_bounds.set_width(0);
  else
    tab_bounds.set_height(0);
  bounds_animator_.AnimateViewTo(tab, tab_bounds);

  // Register delegate to do cleanup when done, BoundsAnimator takes
  // ownership of RemoveTabDelegate.
  bounds_animator_.SetAnimationDelegate(tab, new RemoveTabDelegate(this, tab),
                                        true);
}

void BaseTabStrip::StartMiniTabAnimation() {
  PrepareForAnimation();

  GenerateIdealBounds();
  AnimateToIdealBounds();
}

bool BaseTabStrip::ShouldHighlightCloseButtonAfterRemove() {
  return true;
}

void BaseTabStrip::RemoveAndDeleteTab(BaseTab* tab) {
  int tab_data_index = TabIndexOfTab(tab);

  DCHECK(tab_data_index != -1);

  // Remove the Tab from the TabStrip's list...
  tab_data_.erase(tab_data_.begin() + tab_data_index);

  delete tab;
}

int BaseTabStrip::TabIndexOfTab(BaseTab* tab) const {
  for (int i = 0; i < tab_count(); ++i) {
    if (base_tab_at_tab_index(i) == tab)
      return i;
  }
  return -1;
}

void BaseTabStrip::StopAnimating(bool layout) {
  if (!IsAnimating())
    return;

  bounds_animator().Cancel();

  if (layout)
    DoLayout();
}

void BaseTabStrip::DestroyDragController() {
  if (IsDragSessionActive())
    drag_controller_.reset(NULL);
}

void BaseTabStrip::StartedDraggingTabs(const std::vector<BaseTab*>& tabs) {
  PrepareForAnimation();

  // Reset dragging state of existing tabs.
  for (int i = 0; i < tab_count(); ++i)
    base_tab_at_tab_index(i)->set_dragging(false);

  for (size_t i = 0; i < tabs.size(); ++i) {
    tabs[i]->set_dragging(true);
    bounds_animator_.StopAnimatingView(tabs[i]);
  }

  // Move the dragged tabs to their ideal bounds.
  GenerateIdealBounds();

  // Sets the bounds of the dragged tabs.
  for (size_t i = 0; i < tabs.size(); ++i) {
    int tab_data_index = TabIndexOfTab(tabs[i]);
    DCHECK(tab_data_index != -1);
    tabs[i]->SetBoundsRect(ideal_bounds(tab_data_index));
  }
  SchedulePaint();
}

void BaseTabStrip::StoppedDraggingTabs(const std::vector<BaseTab*>& tabs) {
  bool is_first_tab = true;
  for (size_t i = 0; i < tabs.size(); ++i)
    StoppedDraggingTab(tabs[i], &is_first_tab);
}

void BaseTabStrip::PrepareForAnimation() {
  if (!IsDragSessionActive() && !DraggedTabController::IsAttachedTo(this)) {
    for (int i = 0; i < tab_count(); ++i)
      base_tab_at_tab_index(i)->set_dragging(false);
  }
}

ui::AnimationDelegate* BaseTabStrip::CreateRemoveTabDelegate(BaseTab* tab) {
  return new RemoveTabDelegate(this, tab);
}

void BaseTabStrip::DoLayout() {
  last_layout_size_ = size();

  StopAnimating(false);

  GenerateIdealBounds();

  for (int i = 0; i < tab_count(); ++i)
    tab_data_[i].tab->SetBoundsRect(tab_data_[i].ideal_bounds);

  SchedulePaint();
}

bool BaseTabStrip::IsAnimating() const {
  return bounds_animator_.IsAnimating();
}

BaseTab* BaseTabStrip::GetTabAtLocal(const gfx::Point& local_point) {
  views::View* view = GetEventHandlerForPoint(local_point);
  if (!view)
    return NULL;  // No tab contains the point.

  // Walk up the view hierarchy until we find a tab, or the TabStrip.
  while (view && view != this && view->GetID() != VIEW_ID_TAB)
    view = view->parent();

  return view && view->GetID() == VIEW_ID_TAB ?
      static_cast<BaseTab*>(view) : NULL;
}

void BaseTabStrip::StoppedDraggingTab(BaseTab* tab, bool* is_first_tab) {
  int tab_data_index = TabIndexOfTab(tab);
  if (tab_data_index == -1) {
    // The tab was removed before the drag completed. Don't do anything.
    return;
  }

  if (*is_first_tab) {
    *is_first_tab = false;
    PrepareForAnimation();

    // Animate the view back to its correct position.
    GenerateIdealBounds();
    AnimateToIdealBounds();
  }
  bounds_animator_.AnimateViewTo(tab, ideal_bounds(TabIndexOfTab(tab)));
  // Install a delegate to reset the dragging state when done. We have to leave
  // dragging true for the tab otherwise it'll draw beneath the new tab button.
  bounds_animator_.SetAnimationDelegate(
      tab, new ResetDraggingStateDelegate(tab), true);
}