root/ui/views/controls/table/table_view.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. SwapCompareResult
  2. GetModelIndexToRangeStart
  3. text_background_color_id
  4. selected_text_color_id
  5. width
  6. max_column
  7. in_set_visible_column_width_
  8. SetModel
  9. CreateParentIfNecessary
  10. SetRowBackgroundPainter
  11. SetGrouper
  12. RowCount
  13. SelectedRowCount
  14. Select
  15. FirstSelectedRow
  16. SetColumnVisibility
  17. ToggleSortOrder
  18. IsColumnVisible
  19. AddColumn
  20. HasColumn
  21. SetVisibleColumnWidth
  22. ModelToView
  23. ViewToModel
  24. Layout
  25. GetPreferredSize
  26. OnKeyPressed
  27. OnMousePressed
  28. OnGestureEvent
  29. GetTooltipText
  30. GetTooltipTextOrigin
  31. OnModelChanged
  32. OnItemsChanged
  33. OnItemsAdded
  34. OnItemsRemoved
  35. GetKeyboardContextMenuLocation
  36. OnPaint
  37. OnFocus
  38. OnBlur
  39. NumRowsChanged
  40. SetSortDescriptors
  41. SortItemsAndUpdateMapping
  42. CompareRows
  43. GetRowBounds
  44. GetCellBounds
  45. AdjustCellBoundsForText
  46. CreateHeaderIfNecessary
  47. UpdateVisibleColumnSizes
  48. GetPaintRegion
  49. GetPaintBounds
  50. SchedulePaintForSelection
  51. FindColumnByID
  52. SelectByViewIndex
  53. SetSelectionModel
  54. AdvanceSelection
  55. ConfigureSelectionModelForEvent
  56. SelectRowsInRangeFrom
  57. GetGroupRange
  58. GetTooltipImpl

// Copyright (c) 2012 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 "ui/views/controls/table/table_view.h"

#include <map>

#include "base/auto_reset.h"
#include "base/i18n/rtl.h"
#include "ui/events/event.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/rect_conversions.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/text_utils.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/table/table_grouper.h"
#include "ui/views/controls/table/table_header.h"
#include "ui/views/controls/table/table_utils.h"
#include "ui/views/controls/table/table_view_observer.h"
#include "ui/views/controls/table/table_view_row_background_painter.h"

// Padding around the text (on each side).
static const int kTextVerticalPadding = 3;
static const int kTextHorizontalPadding = 6;

// Size of images.
static const int kImageSize = 16;

static const int kGroupingIndicatorSize = 6;

namespace views {

namespace {

// Returns result, unless ascending is false in which case -result is returned.
int SwapCompareResult(int result, bool ascending) {
  return ascending ? result : -result;
}

// Populates |model_index_to_range_start| based on the |grouper|.
void GetModelIndexToRangeStart(TableGrouper* grouper,
                               int row_count,
                               std::map<int, int>* model_index_to_range_start) {
  for (int model_index = 0; model_index < row_count;) {
    GroupRange range;
    grouper->GetGroupRange(model_index, &range);
    DCHECK_GT(range.length, 0);
    for (int range_counter = 0; range_counter < range.length; range_counter++)
      (*model_index_to_range_start)[range_counter + model_index] = model_index;
    model_index += range.length;
  }
}

// Returns the color id for the background of selected text. |has_focus|
// indicates if the table has focus.
ui::NativeTheme::ColorId text_background_color_id(bool has_focus) {
  return has_focus ?
      ui::NativeTheme::kColorId_TableSelectionBackgroundFocused :
      ui::NativeTheme::kColorId_TableSelectionBackgroundUnfocused;
}

// Returns the color id for text. |has_focus| indicates if the table has focus.
ui::NativeTheme::ColorId selected_text_color_id(bool has_focus) {
  return has_focus ? ui::NativeTheme::kColorId_TableSelectedText :
      ui::NativeTheme::kColorId_TableSelectedTextUnfocused;
}

} // namespace

// Used as the comparator to sort the contents of the table.
struct TableView::SortHelper {
  explicit SortHelper(TableView* table) : table(table) {}

  bool operator()(int model_index1, int model_index2) {
    return table->CompareRows(model_index1, model_index2) < 0;
  }

  TableView* table;
};

// Used as the comparator to sort the contents of the table when a TableGrouper
// is present. When groups are present we sort the groups based on the first row
// in the group and within the groups we keep the same order as the model.
struct TableView::GroupSortHelper {
  explicit GroupSortHelper(TableView* table) : table(table) {}

  bool operator()(int model_index1, int model_index2) {
    const int range1 = model_index_to_range_start[model_index1];
    const int range2 = model_index_to_range_start[model_index2];
    if (range1 == range2) {
      // The two rows are in the same group, sort so that items in the same
      // group always appear in the same order.
      return model_index1 < model_index2;
    }
    return table->CompareRows(range1, range2) < 0;
  }

  TableView* table;
  std::map<int, int> model_index_to_range_start;
};

TableView::VisibleColumn::VisibleColumn() : x(0), width(0) {}

TableView::VisibleColumn::~VisibleColumn() {}

TableView::PaintRegion::PaintRegion()
    : min_row(0),
      max_row(0),
      min_column(0),
      max_column(0) {
}

TableView::PaintRegion::~PaintRegion() {}

TableView::TableView(ui::TableModel* model,
                     const std::vector<ui::TableColumn>& columns,
                     TableTypes table_type,
                     bool single_selection)
    : model_(NULL),
      columns_(columns),
      header_(NULL),
      table_type_(table_type),
      single_selection_(single_selection),
      table_view_observer_(NULL),
      row_height_(font_list_.GetHeight() + kTextVerticalPadding * 2),
      last_parent_width_(0),
      layout_width_(0),
      grouper_(NULL),
      in_set_visible_column_width_(false) {
  for (size_t i = 0; i < columns.size(); ++i) {
    VisibleColumn visible_column;
    visible_column.column = columns[i];
    visible_columns_.push_back(visible_column);
  }
  SetFocusable(true);
  SetModel(model);
}

TableView::~TableView() {
  if (model_)
    model_->SetObserver(NULL);
}

// TODO: this doesn't support arbitrarily changing the model, rename this to
// ClearModel() or something.
void TableView::SetModel(ui::TableModel* model) {
  if (model == model_)
    return;

  if (model_)
    model_->SetObserver(NULL);
  model_ = model;
  selection_model_.Clear();
  if (model_)
    model_->SetObserver(this);
}

View* TableView::CreateParentIfNecessary() {
  ScrollView* scroll_view = ScrollView::CreateScrollViewWithBorder();
  scroll_view->SetContents(this);
  CreateHeaderIfNecessary();
  if (header_)
    scroll_view->SetHeader(header_);
  return scroll_view;
}

void TableView::SetRowBackgroundPainter(
    scoped_ptr<TableViewRowBackgroundPainter> painter) {
  row_background_painter_ = painter.Pass();
}

void TableView::SetGrouper(TableGrouper* grouper) {
  grouper_ = grouper;
  SortItemsAndUpdateMapping();
}

int TableView::RowCount() const {
  return model_ ? model_->RowCount() : 0;
}

int TableView::SelectedRowCount() {
  return static_cast<int>(selection_model_.size());
}

void TableView::Select(int model_row) {
  if (!model_)
    return;

  SelectByViewIndex(model_row == -1 ? -1 : ModelToView(model_row));
}

int TableView::FirstSelectedRow() {
  return SelectedRowCount() == 0 ? -1 : selection_model_.selected_indices()[0];
}

void TableView::SetColumnVisibility(int id, bool is_visible) {
  if (is_visible == IsColumnVisible(id))
    return;

  if (is_visible) {
    VisibleColumn visible_column;
    visible_column.column = FindColumnByID(id);
    visible_columns_.push_back(visible_column);
  } else {
    for (size_t i = 0; i < visible_columns_.size(); ++i) {
      if (visible_columns_[i].column.id == id) {
        visible_columns_.erase(visible_columns_.begin() + i);
        break;
      }
    }
  }
  UpdateVisibleColumnSizes();
  PreferredSizeChanged();
  SchedulePaint();
  if (header_)
    header_->SchedulePaint();
}

void TableView::ToggleSortOrder(int visible_column_index) {
  DCHECK(visible_column_index >= 0 &&
         visible_column_index < static_cast<int>(visible_columns_.size()));
  if (!visible_columns_[visible_column_index].column.sortable)
    return;
  const int column_id = visible_columns_[visible_column_index].column.id;
  SortDescriptors sort(sort_descriptors_);
  if (!sort.empty() && sort[0].column_id == column_id) {
    sort[0].ascending = !sort[0].ascending;
  } else {
    SortDescriptor descriptor(column_id, true);
    sort.insert(sort.begin(), descriptor);
    // Only persist two sort descriptors.
    if (sort.size() > 2)
      sort.resize(2);
  }
  SetSortDescriptors(sort);
}

bool TableView::IsColumnVisible(int id) const {
  for (size_t i = 0; i < visible_columns_.size(); ++i) {
    if (visible_columns_[i].column.id == id)
      return true;
  }
  return false;
}

void TableView::AddColumn(const ui::TableColumn& col) {
  DCHECK(!HasColumn(col.id));
  columns_.push_back(col);
}

bool TableView::HasColumn(int id) const {
  for (size_t i = 0; i < columns_.size(); ++i) {
    if (columns_[i].id == id)
      return true;
  }
  return false;
}

void TableView::SetVisibleColumnWidth(int index, int width) {
  DCHECK(index >= 0 && index < static_cast<int>(visible_columns_.size()));
  if (visible_columns_[index].width == width)
    return;
  base::AutoReset<bool> reseter(&in_set_visible_column_width_, true);
  visible_columns_[index].width = width;
  for (size_t i = index + 1; i < visible_columns_.size(); ++i) {
    visible_columns_[i].x =
        visible_columns_[i - 1].x + visible_columns_[i - 1].width;
  }
  PreferredSizeChanged();
  SchedulePaint();
}

int TableView::ModelToView(int model_index) const {
  if (!is_sorted())
    return model_index;
  DCHECK_GE(model_index, 0) << " negative model_index " << model_index;
  DCHECK_LT(model_index, RowCount()) << " out of bounds model_index " <<
      model_index;
  return model_to_view_[model_index];
}

int TableView::ViewToModel(int view_index) const {
  if (!is_sorted())
    return view_index;
  DCHECK_GE(view_index, 0) << " negative view_index " << view_index;
  DCHECK_LT(view_index, RowCount()) << " out of bounds view_index " <<
      view_index;
  return view_to_model_[view_index];
}

void TableView::Layout() {
  // parent()->parent() is the scrollview. When its width changes we force
  // recalculating column sizes.
  View* scroll_view = parent() ? parent()->parent() : NULL;
  if (scroll_view) {
    const int scroll_view_width = scroll_view->GetContentsBounds().width();
    if (scroll_view_width != last_parent_width_) {
      last_parent_width_ = scroll_view_width;
      if (!in_set_visible_column_width_) {
        // Layout to the parent (the Viewport), which differs from
        // |scroll_view_width| when scrollbars are present.
        layout_width_ = parent()->width();
        UpdateVisibleColumnSizes();
      }
    }
  }
  // We have to override Layout like this since we're contained in a ScrollView.
  gfx::Size pref = GetPreferredSize();
  int width = pref.width();
  int height = pref.height();
  if (parent()) {
    width = std::max(parent()->width(), width);
    height = std::max(parent()->height(), height);
  }
  SetBounds(x(), y(), width, height);
}

gfx::Size TableView::GetPreferredSize() {
  int width = 50;
  if (header_ && !visible_columns_.empty())
    width = visible_columns_.back().x + visible_columns_.back().width;
  return gfx::Size(width, RowCount() * row_height_);
}

bool TableView::OnKeyPressed(const ui::KeyEvent& event) {
  if (!HasFocus())
    return false;

  switch (event.key_code()) {
    case ui::VKEY_A:
      // control-a selects all.
      if (event.IsControlDown() && !single_selection_ && RowCount()) {
        ui::ListSelectionModel selection_model;
        selection_model.SetSelectedIndex(selection_model_.active());
        for (int i = 0; i < RowCount(); ++i)
          selection_model.AddIndexToSelection(i);
        SetSelectionModel(selection_model);
        return true;
      }
      break;

    case ui::VKEY_HOME:
      if (RowCount())
        SelectByViewIndex(0);
      return true;

    case ui::VKEY_END:
      if (RowCount())
        SelectByViewIndex(RowCount() - 1);
      return true;

    case ui::VKEY_UP:
      AdvanceSelection(ADVANCE_DECREMENT);
      return true;

    case ui::VKEY_DOWN:
      AdvanceSelection(ADVANCE_INCREMENT);
      return true;

    default:
      break;
  }
  if (table_view_observer_)
    table_view_observer_->OnKeyDown(event.key_code());
  return false;
}

bool TableView::OnMousePressed(const ui::MouseEvent& event) {
  RequestFocus();
  if (!event.IsOnlyLeftMouseButton())
    return true;

  const int row = event.y() / row_height_;
  if (row < 0 || row >= RowCount())
    return true;

  if (event.GetClickCount() == 2) {
    SelectByViewIndex(row);
    if (table_view_observer_)
      table_view_observer_->OnDoubleClick();
  } else if (event.GetClickCount() == 1) {
    ui::ListSelectionModel new_model;
    ConfigureSelectionModelForEvent(event, &new_model);
    SetSelectionModel(new_model);
  }

  return true;
}

void TableView::OnGestureEvent(ui::GestureEvent* event) {
  if (event->type() != ui::ET_GESTURE_TAP)
    return;

  const int row = event->y() / row_height_;
  if (row < 0 || row >= RowCount())
    return;

  event->StopPropagation();
  ui::ListSelectionModel new_model;
  ConfigureSelectionModelForEvent(*event, &new_model);
  SetSelectionModel(new_model);
}

bool TableView::GetTooltipText(const gfx::Point& p,
                               base::string16* tooltip) const {
  return GetTooltipImpl(p, tooltip, NULL);
}

bool TableView::GetTooltipTextOrigin(const gfx::Point& p,
                                     gfx::Point* loc) const {
  return GetTooltipImpl(p, NULL, loc);
}

void TableView::OnModelChanged() {
  selection_model_.Clear();
  NumRowsChanged();
}

void TableView::OnItemsChanged(int start, int length) {
  SortItemsAndUpdateMapping();
}

void TableView::OnItemsAdded(int start, int length) {
  for (int i = 0; i < length; ++i)
    selection_model_.IncrementFrom(start);
  NumRowsChanged();
}

void TableView::OnItemsRemoved(int start, int length) {
  // Determine the currently selected index in terms of the view. We inline the
  // implementation here since ViewToModel() has DCHECKs that fail since the
  // model has changed but |model_to_view_| has not been updated yet.
  const int previously_selected_model_index = FirstSelectedRow();
  int previously_selected_view_index = previously_selected_model_index;
  if (previously_selected_model_index != -1 && is_sorted())
    previously_selected_view_index =
        model_to_view_[previously_selected_model_index];
  for (int i = 0; i < length; ++i)
    selection_model_.DecrementFrom(start);
  NumRowsChanged();
  // If the selection was empty and is no longer empty select the same visual
  // index.
  if (selection_model_.empty() && previously_selected_view_index != -1 &&
      RowCount()) {
    selection_model_.SetSelectedIndex(
        ViewToModel(std::min(RowCount() - 1, previously_selected_view_index)));
  }
  if (table_view_observer_)
    table_view_observer_->OnSelectionChanged();
}

gfx::Point TableView::GetKeyboardContextMenuLocation() {
  int first_selected = FirstSelectedRow();
  gfx::Rect vis_bounds(GetVisibleBounds());
  int y = vis_bounds.height() / 2;
  if (first_selected != -1) {
    gfx::Rect cell_bounds(GetRowBounds(first_selected));
    if (cell_bounds.bottom() >= vis_bounds.y() &&
        cell_bounds.bottom() < vis_bounds.bottom()) {
      y = cell_bounds.bottom();
    }
  }
  gfx::Point screen_loc(0, y);
  if (base::i18n::IsRTL())
    screen_loc.set_x(width());
  ConvertPointToScreen(this, &screen_loc);
  return screen_loc;
}

void TableView::OnPaint(gfx::Canvas* canvas) {
  // Don't invoke View::OnPaint so that we can render our own focus border.

  canvas->DrawColor(GetNativeTheme()->GetSystemColor(
                        ui::NativeTheme::kColorId_TableBackground));

  if (!RowCount() || visible_columns_.empty())
    return;

  const PaintRegion region(GetPaintRegion(GetPaintBounds(canvas)));
  if (region.min_column == -1)
    return;  // No need to paint anything.

  const SkColor selected_bg_color = GetNativeTheme()->GetSystemColor(
      text_background_color_id(HasFocus()));
  const SkColor fg_color = GetNativeTheme()->GetSystemColor(
      ui::NativeTheme::kColorId_TableText);
  const SkColor selected_fg_color = GetNativeTheme()->GetSystemColor(
      selected_text_color_id(HasFocus()));
  for (int i = region.min_row; i < region.max_row; ++i) {
    const int model_index = ViewToModel(i);
    const bool is_selected = selection_model_.IsSelected(model_index);
    if (is_selected) {
      canvas->FillRect(GetRowBounds(i), selected_bg_color);
    } else if (row_background_painter_) {
      row_background_painter_->PaintRowBackground(model_index,
                                                  GetRowBounds(i),
                                                  canvas);
    }
    if (selection_model_.active() == i && HasFocus())
      canvas->DrawFocusRect(GetRowBounds(i));
    for (int j = region.min_column; j < region.max_column; ++j) {
      const gfx::Rect cell_bounds(GetCellBounds(i, j));
      int text_x = kTextHorizontalPadding + cell_bounds.x();

      // Provide space for the grouping indicator, but draw it separately.
      if (j == 0 && grouper_)
        text_x += kGroupingIndicatorSize + kTextHorizontalPadding;

      // Always paint the icon in the first visible column.
      if (j == 0 && table_type_ == ICON_AND_TEXT) {
        gfx::ImageSkia image = model_->GetIcon(model_index);
        if (!image.isNull()) {
          int image_x = GetMirroredXWithWidthInView(text_x, kImageSize);
          canvas->DrawImageInt(
              image, 0, 0, image.width(), image.height(),
              image_x,
              cell_bounds.y() + (cell_bounds.height() - kImageSize) / 2,
              kImageSize, kImageSize, true);
        }
        text_x += kImageSize + kTextHorizontalPadding;
      }
      if (text_x < cell_bounds.right() - kTextHorizontalPadding) {
        canvas->DrawStringRectWithFlags(
            model_->GetText(model_index, visible_columns_[j].column.id),
            font_list_, is_selected ? selected_fg_color : fg_color,
            gfx::Rect(GetMirroredXWithWidthInView(
                text_x, cell_bounds.right() - text_x - kTextHorizontalPadding),
                      cell_bounds.y() + kTextVerticalPadding,
                      cell_bounds.right() - text_x,
                      cell_bounds.height() - kTextVerticalPadding * 2),
            TableColumnAlignmentToCanvasAlignment(
                visible_columns_[j].column.alignment));
      }
    }
  }

  if (!grouper_ || region.min_column > 0)
    return;

  const SkColor grouping_color = GetNativeTheme()->GetSystemColor(
      ui::NativeTheme::kColorId_TableGroupingIndicatorColor);
  SkPaint grouping_paint;
  grouping_paint.setColor(grouping_color);
  grouping_paint.setStyle(SkPaint::kFill_Style);
  grouping_paint.setAntiAlias(true);
  const int group_indicator_x = GetMirroredXInView(GetCellBounds(0, 0).x() +
      kTextHorizontalPadding + kGroupingIndicatorSize / 2);
  for (int i = region.min_row; i < region.max_row; ) {
    const int model_index = ViewToModel(i);
    GroupRange range;
    grouper_->GetGroupRange(model_index, &range);
    DCHECK_GT(range.length, 0);
    // The order of rows in a group is consistent regardless of sort, so it's ok
    // to do this calculation.
    const int start = i - (model_index - range.start);
    const int last = start + range.length - 1;
    const gfx::Rect start_cell_bounds(GetCellBounds(start, 0));
    if (start != last) {
      const gfx::Rect last_cell_bounds(GetCellBounds(last, 0));
      canvas->FillRect(gfx::Rect(
                           group_indicator_x - kGroupingIndicatorSize / 2,
                           start_cell_bounds.CenterPoint().y(),
                           kGroupingIndicatorSize,
                           last_cell_bounds.y() - start_cell_bounds.y()),
                       grouping_color);
      canvas->DrawCircle(gfx::Point(group_indicator_x,
                                    last_cell_bounds.CenterPoint().y()),
                         kGroupingIndicatorSize / 2, grouping_paint);
    }
    canvas->DrawCircle(gfx::Point(group_indicator_x,
                                  start_cell_bounds.CenterPoint().y()),
                       kGroupingIndicatorSize / 2, grouping_paint);
    i = last + 1;
  }
}

void TableView::OnFocus() {
  SchedulePaintForSelection();
}

void TableView::OnBlur() {
  SchedulePaintForSelection();
}

void TableView::NumRowsChanged() {
  SortItemsAndUpdateMapping();
  PreferredSizeChanged();
  SchedulePaint();
}

void TableView::SetSortDescriptors(const SortDescriptors& sort_descriptors) {
  sort_descriptors_ = sort_descriptors;
  SortItemsAndUpdateMapping();
  if (header_)
    header_->SchedulePaint();
}

void TableView::SortItemsAndUpdateMapping() {
  if (!is_sorted()) {
    view_to_model_.clear();
    model_to_view_.clear();
  } else {
    const int row_count = RowCount();
    view_to_model_.resize(row_count);
    model_to_view_.resize(row_count);
    for (int i = 0; i < row_count; ++i)
      view_to_model_[i] = i;
    if (grouper_) {
      GroupSortHelper sort_helper(this);
      GetModelIndexToRangeStart(grouper_, RowCount(),
                                &sort_helper.model_index_to_range_start);
      std::sort(view_to_model_.begin(), view_to_model_.end(), sort_helper);
    } else {
      std::sort(view_to_model_.begin(), view_to_model_.end(), SortHelper(this));
    }
    for (int i = 0; i < row_count; ++i)
      model_to_view_[view_to_model_[i]] = i;
    model_->ClearCollator();
  }
  SchedulePaint();
}

int TableView::CompareRows(int model_row1, int model_row2) {
  const int sort_result = model_->CompareValues(
      model_row1, model_row2, sort_descriptors_[0].column_id);
  if (sort_result == 0 && sort_descriptors_.size() > 1) {
    // Try the secondary sort.
    return SwapCompareResult(
        model_->CompareValues(model_row1, model_row2,
                              sort_descriptors_[1].column_id),
        sort_descriptors_[1].ascending);
  }
  return SwapCompareResult(sort_result, sort_descriptors_[0].ascending);
}

gfx::Rect TableView::GetRowBounds(int row) const {
  return gfx::Rect(0, row * row_height_, width(), row_height_);
}

gfx::Rect TableView::GetCellBounds(int row, int visible_column_index) const {
  if (!header_)
    return GetRowBounds(row);
  const VisibleColumn& vis_col(visible_columns_[visible_column_index]);
  return gfx::Rect(vis_col.x, row * row_height_, vis_col.width, row_height_);
}

void TableView::AdjustCellBoundsForText(int visible_column_index,
                                        gfx::Rect* bounds) const {
  int text_x = kTextHorizontalPadding + bounds->x();
  if (visible_column_index == 0) {
    if (grouper_)
      text_x += kGroupingIndicatorSize + kTextHorizontalPadding;
    if (table_type_ == ICON_AND_TEXT)
      text_x += kImageSize + kTextHorizontalPadding;
  }
  bounds->set_x(text_x);
  bounds->set_width(
      std::max(0, bounds->right() - kTextHorizontalPadding - text_x));
}

void TableView::CreateHeaderIfNecessary() {
  // Only create a header if there is more than one column or the title of the
  // only column is not empty.
  if (header_ || (columns_.size() == 1 && columns_[0].title.empty()))
    return;

  header_ = new TableHeader(this);
}

void TableView::UpdateVisibleColumnSizes() {
  if (!header_)
    return;

  std::vector<ui::TableColumn> columns;
  for (size_t i = 0; i < visible_columns_.size(); ++i)
    columns.push_back(visible_columns_[i].column);

  int first_column_padding = 0;
  if (table_type_ == ICON_AND_TEXT && header_)
    first_column_padding += kImageSize + kTextHorizontalPadding;
  if (grouper_)
    first_column_padding += kGroupingIndicatorSize + kTextHorizontalPadding;

  std::vector<int> sizes = views::CalculateTableColumnSizes(
      layout_width_, first_column_padding, header_->font_list(), font_list_,
      std::max(kTextHorizontalPadding, TableHeader::kHorizontalPadding) * 2,
      TableHeader::kSortIndicatorWidth, columns, model_);
  DCHECK_EQ(visible_columns_.size(), sizes.size());
  int x = 0;
  for (size_t i = 0; i < visible_columns_.size(); ++i) {
    visible_columns_[i].x = x;
    visible_columns_[i].width = sizes[i];
    x += sizes[i];
  }
}

TableView::PaintRegion TableView::GetPaintRegion(
    const gfx::Rect& bounds) const {
  DCHECK(!visible_columns_.empty());
  DCHECK(RowCount());

  PaintRegion region;
  region.min_row = std::min(RowCount() - 1,
                            std::max(0, bounds.y() / row_height_));
  region.max_row = bounds.bottom() / row_height_;
  if (bounds.bottom() % row_height_ != 0)
    region.max_row++;
  region.max_row = std::min(region.max_row, RowCount());

  if (!header_) {
    region.max_column = 1;
    return region;
  }

  const int paint_x = GetMirroredXForRect(bounds);
  const int paint_max_x = paint_x + bounds.width();
  region.min_column = -1;
  region.max_column = visible_columns_.size();
  for (size_t i = 0; i < visible_columns_.size(); ++i) {
    int max_x = visible_columns_[i].x + visible_columns_[i].width;
    if (region.min_column == -1 && max_x >= paint_x)
      region.min_column = static_cast<int>(i);
    if (region.min_column != -1 && visible_columns_[i].x >= paint_max_x) {
      region.max_column = i;
      break;
    }
  }
  return region;
}

gfx::Rect TableView::GetPaintBounds(gfx::Canvas* canvas) const {
  SkRect sk_clip_rect;
  if (canvas->sk_canvas()->getClipBounds(&sk_clip_rect))
    return gfx::ToEnclosingRect(gfx::SkRectToRectF(sk_clip_rect));
  return GetVisibleBounds();
}

void TableView::SchedulePaintForSelection() {
  if (selection_model_.size() == 1) {
    const int first_model_row = FirstSelectedRow();
    SchedulePaintInRect(GetRowBounds(ModelToView(first_model_row)));
    if (first_model_row != selection_model_.active())
      SchedulePaintInRect(GetRowBounds(ModelToView(selection_model_.active())));
  } else if (selection_model_.size() > 1) {
    SchedulePaint();
  }
}

ui::TableColumn TableView::FindColumnByID(int id) const {
  for (size_t i = 0; i < columns_.size(); ++i) {
    if (columns_[i].id == id)
      return columns_[i];
  }
  NOTREACHED();
  return ui::TableColumn();
}

void TableView::SelectByViewIndex(int view_index) {
  ui::ListSelectionModel new_selection;
  if (view_index != -1) {
    SelectRowsInRangeFrom(view_index, true, &new_selection);
    new_selection.set_anchor(ViewToModel(view_index));
    new_selection.set_active(ViewToModel(view_index));
  }

  SetSelectionModel(new_selection);
}

void TableView::SetSelectionModel(const ui::ListSelectionModel& new_selection) {
  if (new_selection.Equals(selection_model_))
    return;

  SchedulePaintForSelection();
  selection_model_.Copy(new_selection);
  SchedulePaintForSelection();

  // Scroll the group for the active item to visible.
  if (selection_model_.active() != -1) {
    gfx::Rect vis_rect(GetVisibleBounds());
    const GroupRange range(GetGroupRange(selection_model_.active()));
    const int start_y = GetRowBounds(ModelToView(range.start)).y();
    const int end_y =
        GetRowBounds(ModelToView(range.start + range.length - 1)).bottom();
    vis_rect.set_y(start_y);
    vis_rect.set_height(end_y - start_y);
    ScrollRectToVisible(vis_rect);
  }

  if (table_view_observer_)
    table_view_observer_->OnSelectionChanged();
}

void TableView::AdvanceSelection(AdvanceDirection direction) {
  if (selection_model_.active() == -1) {
    SelectByViewIndex(0);
    return;
  }
  int view_index = ModelToView(selection_model_.active());
  if (direction == ADVANCE_DECREMENT)
    view_index = std::max(0, view_index - 1);
  else
    view_index = std::min(RowCount() - 1, view_index + 1);
  SelectByViewIndex(view_index);
}

void TableView::ConfigureSelectionModelForEvent(
    const ui::LocatedEvent& event,
    ui::ListSelectionModel* model) const {
  const int view_index = event.y() / row_height_;
  DCHECK(view_index >= 0 && view_index < RowCount());

  if (selection_model_.anchor() == -1 ||
      single_selection_ ||
      (!event.IsControlDown() && !event.IsShiftDown())) {
    SelectRowsInRangeFrom(view_index, true, model);
    model->set_anchor(ViewToModel(view_index));
    model->set_active(ViewToModel(view_index));
    return;
  }
  if ((event.IsControlDown() && event.IsShiftDown()) || event.IsShiftDown()) {
    // control-shift: copy existing model and make sure rows between anchor and
    // |view_index| are selected.
    // shift: reset selection so that only rows between anchor and |view_index|
    // are selected.
    if (event.IsControlDown() && event.IsShiftDown())
      model->Copy(selection_model_);
    else
      model->set_anchor(selection_model_.anchor());
    for (int i = std::min(view_index, ModelToView(model->anchor())),
             end = std::max(view_index, ModelToView(model->anchor()));
         i <= end; ++i) {
      SelectRowsInRangeFrom(i, true, model);
    }
    model->set_active(ViewToModel(view_index));
  } else {
    DCHECK(event.IsControlDown());
    // Toggle the selection state of |view_index| and set the anchor/active to
    // it and don't change the state of any other rows.
    model->Copy(selection_model_);
    model->set_anchor(ViewToModel(view_index));
    model->set_active(ViewToModel(view_index));
    SelectRowsInRangeFrom(view_index,
                          !model->IsSelected(ViewToModel(view_index)),
                          model);
  }
}

void TableView::SelectRowsInRangeFrom(int view_index,
                                      bool select,
                                      ui::ListSelectionModel* model) const {
  const GroupRange range(GetGroupRange(ViewToModel(view_index)));
  for (int i = 0; i < range.length; ++i) {
    if (select)
      model->AddIndexToSelection(range.start + i);
    else
      model->RemoveIndexFromSelection(range.start + i);
  }
}

GroupRange TableView::GetGroupRange(int model_index) const {
  GroupRange range;
  if (grouper_) {
    grouper_->GetGroupRange(model_index, &range);
  } else {
    range.start = model_index;
    range.length = 1;
  }
  return range;
}

bool TableView::GetTooltipImpl(const gfx::Point& location,
                               base::string16* tooltip,
                               gfx::Point* tooltip_origin) const {
  const int row = location.y() / row_height_;
  if (row < 0 || row >= RowCount() || visible_columns_.empty())
    return false;

  const int x = GetMirroredXInView(location.x());
  const int column = GetClosestVisibleColumnIndex(this, x);
  if (x < visible_columns_[column].x ||
      x > (visible_columns_[column].x + visible_columns_[column].width))
    return false;

  const base::string16 text(model_->GetText(ViewToModel(row),
                                      visible_columns_[column].column.id));
  if (text.empty())
    return false;

  gfx::Rect cell_bounds(GetCellBounds(row, column));
  AdjustCellBoundsForText(column, &cell_bounds);
  const int right = std::min(GetVisibleBounds().right(), cell_bounds.right());
  if (right > cell_bounds.x() &&
      gfx::GetStringWidth(text, font_list_) <= (right - cell_bounds.x()))
    return false;

  if (tooltip)
    *tooltip = text;
  if (tooltip_origin) {
    tooltip_origin->SetPoint(cell_bounds.x(),
                             cell_bounds.y() + kTextVerticalPadding);
  }
  return true;
}

}  // namespace views

/* [<][>][^][v][top][bottom][index][help] */