普通文本  |  468行  |  14.71 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/side_tab_strip.h"

#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/tabs/side_tab.h"
#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "views/background.h"
#include "views/controls/button/image_button.h"
#include "views/controls/button/text_button.h"

namespace {

const int kVerticalTabSpacing = 2;
const int kTabStripWidth = 140;
const SkColor kBackgroundColor = SkColorSetARGB(255, 209, 220, 248);
const SkColor kSeparatorColor = SkColorSetARGB(255, 151, 159, 179);

// Height of the scroll buttons.
const int kScrollButtonHeight = 20;

// Height of the separator.
const int kSeparatorHeight = 1;

// Padding between tabs and scroll button.
const int kScrollButtonVerticalPadding = 2;

// The new tab button is rendered using a SideTab.
class SideTabNewTabButton : public SideTab {
 public:
  explicit SideTabNewTabButton(TabStripController* controller);

  virtual bool ShouldPaintHighlight() const OVERRIDE { return false; }
  virtual bool IsSelected() const OVERRIDE { return false; }
  virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE;
  virtual void OnMouseReleased(const views::MouseEvent& event) OVERRIDE;

 private:
  TabStripController* controller_;

  DISALLOW_COPY_AND_ASSIGN(SideTabNewTabButton);
};

SideTabNewTabButton::SideTabNewTabButton(TabStripController* controller)
    : SideTab(NULL),
      controller_(controller) {
  // Never show a close button for the new tab button.
  close_button()->SetVisible(false);
  TabRendererData data;
  data.favicon = *ResourceBundle::GetSharedInstance().GetBitmapNamed(
      IDR_SIDETABS_NEW_TAB);
  data.title = l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE);
  SetData(data);
}

bool SideTabNewTabButton::OnMousePressed(const views::MouseEvent& event) {
  return true;
}

void SideTabNewTabButton::OnMouseReleased(const views::MouseEvent& event) {
  if (event.IsOnlyLeftMouseButton() && HitTest(event.location()))
    controller_->CreateNewTab();
}

// Button class used for the scroll buttons.
class ScrollButton : public views::TextButton {
 public:
  enum Type {
    UP,
    DOWN
  };

  explicit ScrollButton(views::ButtonListener* listener, Type type);

 protected:
  // views::View overrides.
  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;

 private:
  const Type type_;

  DISALLOW_COPY_AND_ASSIGN(ScrollButton);
};

ScrollButton::ScrollButton(views::ButtonListener* listener,
                           Type type)
    : views::TextButton(listener, std::wstring()),
      type_(type) {
}

void ScrollButton::OnPaint(gfx::Canvas* canvas) {
  TextButton::OnPaint(canvas);

  // Draw the arrow.
  SkColor arrow_color = IsEnabled() ? SK_ColorBLACK : SK_ColorGRAY;
  int arrow_height = 5;
  int x = width() / 2;
  int y = (height() - arrow_height) / 2;
  int delta_y = 1;
  if (type_ == DOWN) {
    delta_y = -1;
    y += arrow_height;
  }
  for (int i = 0; i < arrow_height; ++i, --x, y += delta_y)
    canvas->FillRectInt(arrow_color, x, y, (i * 2) + 1, 1);
}

}  // namespace

// static
const int SideTabStrip::kTabStripInset = 3;

////////////////////////////////////////////////////////////////////////////////
// SideTabStrip, public:

SideTabStrip::SideTabStrip(TabStripController* controller)
    : BaseTabStrip(controller, BaseTabStrip::VERTICAL_TAB_STRIP),
      newtab_button_(new SideTabNewTabButton(controller)),
      scroll_up_button_(NULL),
      scroll_down_button_(NULL),
      separator_(new views::View()),
      first_tab_y_offset_(0),
      ideal_height_(0) {
  SetID(VIEW_ID_TAB_STRIP);
  set_background(views::Background::CreateSolidBackground(kBackgroundColor));
  AddChildView(newtab_button_);
  separator_->set_background(
      views::Background::CreateSolidBackground(kSeparatorColor));
  AddChildView(separator_);
  scroll_up_button_ = new ScrollButton(this, ScrollButton::UP);
  AddChildView(scroll_up_button_);
  scroll_down_button_ = new ScrollButton(this, ScrollButton::DOWN);
  AddChildView(scroll_down_button_);
}

SideTabStrip::~SideTabStrip() {
  DestroyDragController();
}

////////////////////////////////////////////////////////////////////////////////
// SideTabStrip, AbstractTabStripView implementation:

bool SideTabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
  return GetEventHandlerForPoint(point) == this;
}

void SideTabStrip::SetBackgroundOffset(const gfx::Point& offset) {
}

////////////////////////////////////////////////////////////////////////////////
// SideTabStrip, BaseTabStrip implementation:

void SideTabStrip::StartHighlight(int model_index) {
}

void SideTabStrip::StopAllHighlighting() {
}

BaseTab* SideTabStrip::CreateTabForDragging() {
  SideTab* tab = new SideTab(NULL);
  // Make sure the dragged tab shares our theme provider. We need to explicitly
  // do this as during dragging there isn't a theme provider.
  tab->set_theme_provider(GetThemeProvider());
  return tab;
}

void SideTabStrip::RemoveTabAt(int model_index) {
  StartRemoveTabAnimation(model_index);
}

void SideTabStrip::SelectTabAt(int old_model_index, int new_model_index) {
  GetBaseTabAtModelIndex(new_model_index)->SchedulePaint();

  if (controller()->IsActiveTab(new_model_index))
    MakeTabVisible(ModelIndexToTabIndex(new_model_index));
}

void SideTabStrip::TabTitleChangedNotLoading(int model_index) {
}

gfx::Size SideTabStrip::GetPreferredSize() {
  return gfx::Size(kTabStripWidth, 0);
}

void SideTabStrip::PaintChildren(gfx::Canvas* canvas) {
  // Make sure any tabs being dragged appear on top of all others by painting
  // them last.
  std::vector<BaseTab*> dragging_tabs;

  // Make sure nothing draws on top of the scroll buttons.
  canvas->Save();
  canvas->ClipRectInt(kTabStripInset, kTabStripInset,
                      width() - kTabStripInset - kTabStripInset,
                      GetMaxTabY() - kTabStripInset);

  // Paint the new tab and separator first so that any tabs animating appear on
  // top.
  separator_->Paint(canvas);
  newtab_button_->Paint(canvas);

  for (int i = tab_count() - 1; i >= 0; --i) {
    BaseTab* tab = base_tab_at_tab_index(i);
    if (tab->dragging())
      dragging_tabs.push_back(tab);
    else
      tab->Paint(canvas);
  }

  for (size_t i = 0; i < dragging_tabs.size(); ++i)
    dragging_tabs[i]->Paint(canvas);

  canvas->Restore();

  scroll_down_button_->Paint(canvas);
  scroll_up_button_->Paint(canvas);
}

views::View* SideTabStrip::GetEventHandlerForPoint(const gfx::Point& point) {
  // Check the scroll buttons first as they visually appear on top of everything
  // else.
  if (scroll_down_button_->IsVisible()) {
    gfx::Point local_point(point);
    View::ConvertPointToView(this, scroll_down_button_, &local_point);
    if (scroll_down_button_->HitTest(local_point))
      return scroll_down_button_->GetEventHandlerForPoint(local_point);
  }

  if (scroll_up_button_->IsVisible()) {
    gfx::Point local_point(point);
    View::ConvertPointToView(this, scroll_up_button_, &local_point);
    if (scroll_up_button_->HitTest(local_point))
      return scroll_up_button_->GetEventHandlerForPoint(local_point);
  }
  return views::View::GetEventHandlerForPoint(point);
}

void SideTabStrip::ButtonPressed(views::Button* sender,
                                 const views::Event& event) {
  int max_offset = GetMaxOffset();
  if (max_offset == 0) {
    // All the tabs fit, no need to scroll.
    return;
  }

  // Determine the index of the first visible tab.
  int initial_y = kTabStripInset;
  int first_vis_index = -1;
  for (int i = 0; i < tab_count(); ++i) {
    if (ideal_bounds(i).bottom() > initial_y) {
      first_vis_index = i;
      break;
    }
  }
  if (first_vis_index == -1)
    return;

  int delta = 0;
  if (sender == scroll_up_button_) {
    delta = initial_y - ideal_bounds(first_vis_index).y();
    if (delta <= 0) {
      if (first_vis_index == 0) {
        delta = -first_tab_y_offset_;
      } else {
        delta = initial_y - ideal_bounds(first_vis_index - 1).y();
        DCHECK_NE(0, delta);  // Not fatal, but indicates we aren't scrolling.
      }
    }
  } else {
    DCHECK_EQ(sender, scroll_down_button_);
    if (ideal_bounds(first_vis_index).y() > initial_y) {
      delta = initial_y - ideal_bounds(first_vis_index).y();
    } else if (first_vis_index + 1 == tab_count()) {
      delta = -first_tab_y_offset_;
    } else {
      delta = initial_y - ideal_bounds(first_vis_index + 1).y();
    }
  }
  SetFirstTabYOffset(first_tab_y_offset_ + delta);
}

BaseTab* SideTabStrip::CreateTab() {
  return new SideTab(this);
}

void SideTabStrip::GenerateIdealBounds() {
  gfx::Rect layout_rect = GetContentsBounds();
  layout_rect.Inset(kTabStripInset, kTabStripInset);

  int y = layout_rect.y() + first_tab_y_offset_;
  bool last_was_mini = true;
  bool has_non_closing_tab = false;
  separator_bounds_.SetRect(0, -kSeparatorHeight, width(), kSeparatorHeight);
  for (int i = 0; i < tab_count(); ++i) {
    BaseTab* tab = base_tab_at_tab_index(i);
    if (!tab->closing()) {
      if (last_was_mini != tab->data().mini) {
        if (has_non_closing_tab) {
          separator_bounds_.SetRect(0, y, width(), kSeparatorHeight);
          y += kSeparatorHeight + kVerticalTabSpacing;
        }
        newtab_button_bounds_.SetRect(
            layout_rect.x(), y, layout_rect.width(),
            newtab_button_->GetPreferredSize().height());
        y = newtab_button_bounds_.bottom() + kVerticalTabSpacing;
        last_was_mini = tab->data().mini;
      }
      gfx::Rect bounds = gfx::Rect(layout_rect.x(), y, layout_rect.width(),
                                   tab->GetPreferredSize().height());
      set_ideal_bounds(i, bounds);
      y = bounds.bottom() + kVerticalTabSpacing;
      has_non_closing_tab = true;
    }
  }

  if (last_was_mini) {
    if (has_non_closing_tab) {
      separator_bounds_.SetRect(0, y, width(), kSeparatorHeight);
      y += kSeparatorHeight + kVerticalTabSpacing;
    }
    newtab_button_bounds_ =
        gfx::Rect(layout_rect.x(), y, layout_rect.width(),
                  newtab_button_->GetPreferredSize().height());
    y += newtab_button_->GetPreferredSize().height();
  }

  ideal_height_ = y - layout_rect.y() - first_tab_y_offset_;

  scroll_up_button_->SetEnabled(first_tab_y_offset_ != 0);
  scroll_down_button_->SetEnabled(GetMaxOffset() != 0 &&
                                  first_tab_y_offset_ != GetMaxOffset());
}

void SideTabStrip::StartInsertTabAnimation(int model_index) {
  PrepareForAnimation();

  GenerateIdealBounds();

  int tab_data_index = ModelIndexToTabIndex(model_index);
  BaseTab* tab = base_tab_at_tab_index(tab_data_index);
  if (model_index == 0) {
    tab->SetBounds(ideal_bounds(tab_data_index).x(), 0,
                   ideal_bounds(tab_data_index).width(), 0);
  } else {
    BaseTab* last_tab = base_tab_at_tab_index(tab_data_index - 1);
    tab->SetBounds(last_tab->x(), last_tab->bounds().bottom(),
                   ideal_bounds(tab_data_index).width(), 0);
  }

  AnimateToIdealBounds();
}

void SideTabStrip::AnimateToIdealBounds() {
  for (int i = 0; i < tab_count(); ++i) {
    BaseTab* tab = base_tab_at_tab_index(i);
    if (!tab->closing() && !tab->dragging())
      bounds_animator().AnimateViewTo(tab, ideal_bounds(i));
  }

  bounds_animator().AnimateViewTo(newtab_button_, newtab_button_bounds_);

  bounds_animator().AnimateViewTo(separator_, separator_bounds_);
}

void SideTabStrip::DoLayout() {
  BaseTabStrip::DoLayout();
  newtab_button_->SetBoundsRect(newtab_button_bounds_);
  separator_->SetBoundsRect(separator_bounds_);
  int scroll_button_y = height() - kScrollButtonHeight;
  scroll_up_button_->SetBounds(0, scroll_button_y, width() / 2,
                               kScrollButtonHeight);
  scroll_down_button_->SetBounds(width() / 2, scroll_button_y, width() / 2,
                                 kScrollButtonHeight);
}

void SideTabStrip::LayoutDraggedTabsAt(const std::vector<BaseTab*>& tabs,
                                       BaseTab* active_tab,
                                       const gfx::Point& location,
                                       bool initial_drag) {
  // TODO: add support for initial_drag (see TabStrip's implementation).
  gfx::Rect layout_rect = GetContentsBounds();
  layout_rect.Inset(kTabStripInset, kTabStripInset);
  int y = location.y();
  for (size_t i = 0; i < tabs.size(); ++i) {
    BaseTab* tab = tabs[i];
    tab->SchedulePaint();
    tab->SetBounds(layout_rect.x(), y, layout_rect.width(),
                   tab->GetPreferredSize().height());
    tab->SchedulePaint();
    y += tab->height() + kVerticalTabSpacing;
  }
}

void SideTabStrip::CalculateBoundsForDraggedTabs(
    const std::vector<BaseTab*>& tabs,
    std::vector<gfx::Rect>* bounds) {
  int y = 0;
  for (size_t i = 0; i < tabs.size(); ++i) {
    BaseTab* tab = tabs[i];
    gfx::Rect tab_bounds(tab->bounds());
    tab_bounds.set_y(y);
    y += tab->height() + kVerticalTabSpacing;
    bounds->push_back(tab_bounds);
  }
}

int SideTabStrip::GetSizeNeededForTabs(const std::vector<BaseTab*>& tabs) {
  return static_cast<int>(tabs.size()) * SideTab::GetPreferredHeight();
}

void SideTabStrip::OnBoundsChanged(const gfx::Rect& previous_bounds) {
  // When our height changes we may be able to show more.
  first_tab_y_offset_ = std::max(GetMaxOffset(),
                                 std::min(0, first_tab_y_offset_));
  for (int i = 0; i < controller()->GetCount(); ++i) {
    if (controller()->IsActiveTab(i)) {
      MakeTabVisible(ModelIndexToTabIndex(i));
      break;
    }
  }
}

void SideTabStrip::SetFirstTabYOffset(int new_offset) {
  int max_offset = GetMaxOffset();
  if (max_offset == 0) {
    // All the tabs fit, no need to scroll.
    return;
  }
  new_offset = std::max(max_offset, std::min(0, new_offset));
  if (new_offset == first_tab_y_offset_)
    return;

  StopAnimating(false);
  first_tab_y_offset_ = new_offset;
  GenerateIdealBounds();
  DoLayout();

}

int SideTabStrip::GetMaxOffset() const {
  int available_height = GetMaxTabY() - kTabStripInset;
  return std::min(0, available_height - ideal_height_);
}

int SideTabStrip::GetMaxTabY() const {
  return height() - kTabStripInset - kScrollButtonVerticalPadding -
      kScrollButtonHeight;
}

void SideTabStrip::MakeTabVisible(int tab_index) {
  if (height() == 0)
    return;

  if (ideal_bounds(tab_index).y() < kTabStripInset) {
    SetFirstTabYOffset(first_tab_y_offset_ - ideal_bounds(tab_index).y() +
                       kTabStripInset);
  } else if (ideal_bounds(tab_index).bottom() > GetMaxTabY()) {
    SetFirstTabYOffset(GetMaxTabY() - (ideal_bounds(tab_index).bottom() -
                                       first_tab_y_offset_));
  }
}