1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/views/controls/table/table_view.h"
6
7#include <map>
8
9#include "base/auto_reset.h"
10#include "base/i18n/rtl.h"
11#include "ui/base/events/event.h"
12#include "ui/gfx/canvas.h"
13#include "ui/gfx/image/image_skia.h"
14#include "ui/gfx/rect_conversions.h"
15#include "ui/gfx/skia_util.h"
16#include "ui/native_theme/native_theme.h"
17#include "ui/views/controls/scroll_view.h"
18#include "ui/views/controls/table/table_grouper.h"
19#include "ui/views/controls/table/table_header.h"
20#include "ui/views/controls/table/table_utils.h"
21#include "ui/views/controls/table/table_view_observer.h"
22#include "ui/views/controls/table/table_view_row_background_painter.h"
23
24// Padding around the text (on each side).
25static const int kTextVerticalPadding = 3;
26static const int kTextHorizontalPadding = 6;
27
28// Size of images.
29static const int kImageSize = 16;
30
31static const int kGroupingIndicatorSize = 6;
32
33namespace views {
34
35namespace {
36
37// Returns result, unless ascending is false in which case -result is returned.
38int SwapCompareResult(int result, bool ascending) {
39  return ascending ? result : -result;
40}
41
42// Populates |model_index_to_range_start| based on the |grouper|.
43void GetModelIndexToRangeStart(TableGrouper* grouper,
44                               int row_count,
45                               std::map<int, int>* model_index_to_range_start) {
46  for (int model_index = 0; model_index < row_count;) {
47    GroupRange range;
48    grouper->GetGroupRange(model_index, &range);
49    DCHECK_GT(range.length, 0);
50    for (int range_counter = 0; range_counter < range.length; range_counter++)
51      (*model_index_to_range_start)[range_counter + model_index] = model_index;
52    model_index += range.length;
53  }
54}
55
56// Returns the color id for the background of selected text. |has_focus|
57// indicates if the table has focus.
58ui::NativeTheme::ColorId text_background_color_id(bool has_focus) {
59  return has_focus ?
60      ui::NativeTheme::kColorId_TableSelectionBackgroundFocused :
61      ui::NativeTheme::kColorId_TableSelectionBackgroundUnfocused;
62}
63
64// Returns the color id for text. |has_focus| indicates if the table has focus.
65ui::NativeTheme::ColorId selected_text_color_id(bool has_focus) {
66  return has_focus ? ui::NativeTheme::kColorId_TableSelectedText :
67      ui::NativeTheme::kColorId_TableSelectedTextUnfocused;
68}
69
70} // namespace
71
72// Used as the comparator to sort the contents of the table.
73struct TableView::SortHelper {
74  explicit SortHelper(TableView* table) : table(table) {}
75
76  bool operator()(int model_index1, int model_index2) {
77    return table->CompareRows(model_index1, model_index2) < 0;
78  }
79
80  TableView* table;
81};
82
83// Used as the comparator to sort the contents of the table when a TableGrouper
84// is present. When groups are present we sort the groups based on the first row
85// in the group and within the groups we keep the same order as the model.
86struct TableView::GroupSortHelper {
87  explicit GroupSortHelper(TableView* table) : table(table) {}
88
89  bool operator()(int model_index1, int model_index2) {
90    const int range1 = model_index_to_range_start[model_index1];
91    const int range2 = model_index_to_range_start[model_index2];
92    if (range1 == range2) {
93      // The two rows are in the same group, sort so that items in the same
94      // group always appear in the same order.
95      return model_index1 < model_index2;
96    }
97    return table->CompareRows(range1, range2) < 0;
98  }
99
100  TableView* table;
101  std::map<int, int> model_index_to_range_start;
102};
103
104TableView::VisibleColumn::VisibleColumn() : x(0), width(0) {}
105
106TableView::VisibleColumn::~VisibleColumn() {}
107
108TableView::PaintRegion::PaintRegion()
109    : min_row(0),
110      max_row(0),
111      min_column(0),
112      max_column(0) {
113}
114
115TableView::PaintRegion::~PaintRegion() {}
116
117TableView::TableView(ui::TableModel* model,
118                     const std::vector<ui::TableColumn>& columns,
119                     TableTypes table_type,
120                     bool single_selection)
121    : model_(NULL),
122      columns_(columns),
123      header_(NULL),
124      table_type_(table_type),
125      single_selection_(single_selection),
126      table_view_observer_(NULL),
127      row_height_(font_.GetHeight() + kTextVerticalPadding * 2),
128      last_parent_width_(0),
129      layout_width_(0),
130      grouper_(NULL),
131      in_set_visible_column_width_(false) {
132  for (size_t i = 0; i < columns.size(); ++i) {
133    VisibleColumn visible_column;
134    visible_column.column = columns[i];
135    visible_columns_.push_back(visible_column);
136  }
137  set_focusable(true);
138  SetModel(model);
139}
140
141TableView::~TableView() {
142  if (model_)
143    model_->SetObserver(NULL);
144}
145
146// TODO: this doesn't support arbitrarily changing the model, rename this to
147// ClearModel() or something.
148void TableView::SetModel(ui::TableModel* model) {
149  if (model == model_)
150    return;
151
152  if (model_)
153    model_->SetObserver(NULL);
154  model_ = model;
155  selection_model_.Clear();
156  if (model_)
157    model_->SetObserver(this);
158}
159
160View* TableView::CreateParentIfNecessary() {
161  ScrollView* scroll_view = ScrollView::CreateScrollViewWithBorder();
162  scroll_view->SetContents(this);
163  CreateHeaderIfNecessary();
164  if (header_)
165    scroll_view->SetHeader(header_);
166  return scroll_view;
167}
168
169void TableView::SetRowBackgroundPainter(
170    scoped_ptr<TableViewRowBackgroundPainter> painter) {
171  row_background_painter_ = painter.Pass();
172}
173
174void TableView::SetGrouper(TableGrouper* grouper) {
175  grouper_ = grouper;
176  SortItemsAndUpdateMapping();
177}
178
179int TableView::RowCount() const {
180  return model_ ? model_->RowCount() : 0;
181}
182
183int TableView::SelectedRowCount() {
184  return static_cast<int>(selection_model_.size());
185}
186
187void TableView::Select(int model_row) {
188  if (!model_)
189    return;
190
191  SelectByViewIndex(model_row == -1 ? -1 : ModelToView(model_row));
192}
193
194int TableView::FirstSelectedRow() {
195  return SelectedRowCount() == 0 ? -1 : selection_model_.selected_indices()[0];
196}
197
198void TableView::SetColumnVisibility(int id, bool is_visible) {
199  if (is_visible == IsColumnVisible(id))
200    return;
201
202  if (is_visible) {
203    VisibleColumn visible_column;
204    visible_column.column = FindColumnByID(id);
205    visible_columns_.push_back(visible_column);
206  } else {
207    for (size_t i = 0; i < visible_columns_.size(); ++i) {
208      if (visible_columns_[i].column.id == id) {
209        visible_columns_.erase(visible_columns_.begin() + i);
210        break;
211      }
212    }
213  }
214  UpdateVisibleColumnSizes();
215  PreferredSizeChanged();
216  SchedulePaint();
217  if (header_)
218    header_->SchedulePaint();
219}
220
221void TableView::ToggleSortOrder(int visible_column_index) {
222  DCHECK(visible_column_index >= 0 &&
223         visible_column_index < static_cast<int>(visible_columns_.size()));
224  if (!visible_columns_[visible_column_index].column.sortable)
225    return;
226  const int column_id = visible_columns_[visible_column_index].column.id;
227  SortDescriptors sort(sort_descriptors_);
228  if (!sort.empty() && sort[0].column_id == column_id) {
229    sort[0].ascending = !sort[0].ascending;
230  } else {
231    SortDescriptor descriptor(column_id, true);
232    sort.insert(sort.begin(), descriptor);
233    // Only persist two sort descriptors.
234    if (sort.size() > 2)
235      sort.resize(2);
236  }
237  SetSortDescriptors(sort);
238}
239
240bool TableView::IsColumnVisible(int id) const {
241  for (size_t i = 0; i < visible_columns_.size(); ++i) {
242    if (visible_columns_[i].column.id == id)
243      return true;
244  }
245  return false;
246}
247
248void TableView::AddColumn(const ui::TableColumn& col) {
249  DCHECK(!HasColumn(col.id));
250  columns_.push_back(col);
251}
252
253bool TableView::HasColumn(int id) const {
254  for (size_t i = 0; i < columns_.size(); ++i) {
255    if (columns_[i].id == id)
256      return true;
257  }
258  return false;
259}
260
261void TableView::SetVisibleColumnWidth(int index, int width) {
262  DCHECK(index >= 0 && index < static_cast<int>(visible_columns_.size()));
263  if (visible_columns_[index].width == width)
264    return;
265  base::AutoReset<bool> reseter(&in_set_visible_column_width_, true);
266  visible_columns_[index].width = width;
267  for (size_t i = index + 1; i < visible_columns_.size(); ++i) {
268    visible_columns_[i].x =
269        visible_columns_[i - 1].x + visible_columns_[i - 1].width;
270  }
271  PreferredSizeChanged();
272  SchedulePaint();
273}
274
275int TableView::ModelToView(int model_index) const {
276  if (!is_sorted())
277    return model_index;
278  DCHECK_GE(model_index, 0) << " negative model_index " << model_index;
279  DCHECK_LT(model_index, RowCount()) << " out of bounds model_index " <<
280      model_index;
281  return model_to_view_[model_index];
282}
283
284int TableView::ViewToModel(int view_index) const {
285  if (!is_sorted())
286    return view_index;
287  DCHECK_GE(view_index, 0) << " negative view_index " << view_index;
288  DCHECK_LT(view_index, RowCount()) << " out of bounds view_index " <<
289      view_index;
290  return view_to_model_[view_index];
291}
292
293void TableView::Layout() {
294  // parent()->parent() is the scrollview. When its width changes we force
295  // recalculating column sizes.
296  View* scroll_view = parent() ? parent()->parent() : NULL;
297  if (scroll_view) {
298    const int scroll_view_width = scroll_view->GetContentsBounds().width();
299    if (scroll_view_width != last_parent_width_) {
300      last_parent_width_ = scroll_view_width;
301      if (!in_set_visible_column_width_) {
302        // Layout to the parent (the Viewport), which differs from
303        // |scroll_view_width| when scrollbars are present.
304        layout_width_ = parent()->width();
305        UpdateVisibleColumnSizes();
306      }
307    }
308  }
309  // We have to override Layout like this since we're contained in a ScrollView.
310  gfx::Size pref = GetPreferredSize();
311  int width = pref.width();
312  int height = pref.height();
313  if (parent()) {
314    width = std::max(parent()->width(), width);
315    height = std::max(parent()->height(), height);
316  }
317  SetBounds(x(), y(), width, height);
318}
319
320gfx::Size TableView::GetPreferredSize() {
321  int width = 50;
322  if (header_ && !visible_columns_.empty())
323    width = visible_columns_.back().x + visible_columns_.back().width;
324  return gfx::Size(width, RowCount() * row_height_);
325}
326
327bool TableView::OnKeyPressed(const ui::KeyEvent& event) {
328  if (!HasFocus())
329    return false;
330
331  switch (event.key_code()) {
332    case ui::VKEY_A:
333      // control-a selects all.
334      if (event.IsControlDown() && !single_selection_ && RowCount()) {
335        ui::ListSelectionModel selection_model;
336        selection_model.SetSelectedIndex(selection_model_.active());
337        for (int i = 0; i < RowCount(); ++i)
338          selection_model.AddIndexToSelection(i);
339        SetSelectionModel(selection_model);
340        return true;
341      }
342      break;
343
344    case ui::VKEY_HOME:
345      if (RowCount())
346        SelectByViewIndex(0);
347      return true;
348
349    case ui::VKEY_END:
350      if (RowCount())
351        SelectByViewIndex(RowCount() - 1);
352      return true;
353
354    case ui::VKEY_UP:
355      AdvanceSelection(ADVANCE_DECREMENT);
356      return true;
357
358    case ui::VKEY_DOWN:
359      AdvanceSelection(ADVANCE_INCREMENT);
360      return true;
361
362    default:
363      break;
364  }
365  if (table_view_observer_)
366    table_view_observer_->OnKeyDown(event.key_code());
367  return false;
368}
369
370bool TableView::OnMousePressed(const ui::MouseEvent& event) {
371  RequestFocus();
372  if (!event.IsOnlyLeftMouseButton())
373    return true;
374
375  const int row = event.y() / row_height_;
376  if (row < 0 || row >= RowCount())
377    return true;
378
379  if (event.GetClickCount() == 2) {
380    SelectByViewIndex(row);
381    if (table_view_observer_)
382      table_view_observer_->OnDoubleClick();
383  } else if (event.GetClickCount() == 1) {
384    ui::ListSelectionModel new_model;
385    ConfigureSelectionModelForEvent(event, &new_model);
386    SetSelectionModel(new_model);
387  }
388
389  return true;
390}
391
392bool TableView::GetTooltipText(const gfx::Point& p,
393                               string16* tooltip) const {
394  return GetTooltipImpl(p, tooltip, NULL);
395}
396
397bool TableView::GetTooltipTextOrigin(const gfx::Point& p,
398                                     gfx::Point* loc) const {
399  return GetTooltipImpl(p, NULL, loc);
400}
401
402void TableView::OnModelChanged() {
403  selection_model_.Clear();
404  NumRowsChanged();
405}
406
407void TableView::OnItemsChanged(int start, int length) {
408  SortItemsAndUpdateMapping();
409}
410
411void TableView::OnItemsAdded(int start, int length) {
412  for (int i = 0; i < length; ++i)
413    selection_model_.IncrementFrom(start);
414  NumRowsChanged();
415}
416
417void TableView::OnItemsRemoved(int start, int length) {
418  // Determine the currently selected index in terms of the view. We inline the
419  // implementation here since ViewToModel() has DCHECKs that fail since the
420  // model has changed but |model_to_view_| has not been updated yet.
421  const int previously_selected_model_index = FirstSelectedRow();
422  int previously_selected_view_index = previously_selected_model_index;
423  if (previously_selected_model_index != -1 && is_sorted())
424    previously_selected_view_index =
425        model_to_view_[previously_selected_model_index];
426  for (int i = 0; i < length; ++i)
427    selection_model_.DecrementFrom(start);
428  NumRowsChanged();
429  // If the selection was empty and is no longer empty select the same visual
430  // index.
431  if (selection_model_.empty() && previously_selected_view_index != -1 &&
432      RowCount()) {
433    selection_model_.SetSelectedIndex(
434        ViewToModel(std::min(RowCount() - 1, previously_selected_view_index)));
435  }
436  if (table_view_observer_)
437    table_view_observer_->OnSelectionChanged();
438}
439
440gfx::Point TableView::GetKeyboardContextMenuLocation() {
441  int first_selected = FirstSelectedRow();
442  gfx::Rect vis_bounds(GetVisibleBounds());
443  int y = vis_bounds.height() / 2;
444  if (first_selected != -1) {
445    gfx::Rect cell_bounds(GetRowBounds(first_selected));
446    if (cell_bounds.bottom() >= vis_bounds.y() &&
447        cell_bounds.bottom() < vis_bounds.bottom()) {
448      y = cell_bounds.bottom();
449    }
450  }
451  gfx::Point screen_loc(0, y);
452  if (base::i18n::IsRTL())
453    screen_loc.set_x(width());
454  ConvertPointToScreen(this, &screen_loc);
455  return screen_loc;
456}
457
458void TableView::OnPaint(gfx::Canvas* canvas) {
459  // Don't invoke View::OnPaint so that we can render our own focus border.
460
461  canvas->DrawColor(GetNativeTheme()->GetSystemColor(
462                        ui::NativeTheme::kColorId_TableBackground));
463
464  if (!RowCount() || visible_columns_.empty())
465    return;
466
467  const PaintRegion region(GetPaintRegion(GetPaintBounds(canvas)));
468  if (region.min_column == -1)
469    return;  // No need to paint anything.
470
471  const SkColor selected_bg_color = GetNativeTheme()->GetSystemColor(
472      text_background_color_id(HasFocus()));
473  const SkColor fg_color = GetNativeTheme()->GetSystemColor(
474      ui::NativeTheme::kColorId_TableText);
475  const SkColor selected_fg_color = GetNativeTheme()->GetSystemColor(
476      selected_text_color_id(HasFocus()));
477  for (int i = region.min_row; i < region.max_row; ++i) {
478    const int model_index = ViewToModel(i);
479    const bool is_selected = selection_model_.IsSelected(model_index);
480    if (is_selected) {
481      canvas->FillRect(GetRowBounds(i), selected_bg_color);
482    } else if (row_background_painter_) {
483      row_background_painter_->PaintRowBackground(model_index,
484                                                  GetRowBounds(i),
485                                                  canvas);
486    }
487    if (selection_model_.active() == i && HasFocus())
488      canvas->DrawFocusRect(GetRowBounds(i));
489    for (int j = region.min_column; j < region.max_column; ++j) {
490      const gfx::Rect cell_bounds(GetCellBounds(i, j));
491      int text_x = kTextHorizontalPadding + cell_bounds.x();
492
493      // Provide space for the grouping indicator, but draw it separately.
494      if (j == 0 && grouper_)
495        text_x += kGroupingIndicatorSize + kTextHorizontalPadding;
496
497      // Always paint the icon in the first visible column.
498      if (j == 0 && table_type_ == ICON_AND_TEXT) {
499        gfx::ImageSkia image = model_->GetIcon(model_index);
500        if (!image.isNull()) {
501          int image_x = GetMirroredXWithWidthInView(text_x, kImageSize);
502          canvas->DrawImageInt(
503              image, 0, 0, image.width(), image.height(),
504              image_x,
505              cell_bounds.y() + (cell_bounds.height() - kImageSize) / 2,
506              kImageSize, kImageSize, true);
507        }
508        text_x += kImageSize + kTextHorizontalPadding;
509      }
510      if (text_x < cell_bounds.right() - kTextHorizontalPadding) {
511        canvas->DrawStringInt(
512            model_->GetText(model_index, visible_columns_[j].column.id), font_,
513            is_selected ? selected_fg_color : fg_color,
514            GetMirroredXWithWidthInView(text_x, cell_bounds.right() - text_x -
515                                        kTextHorizontalPadding),
516            cell_bounds.y() + kTextVerticalPadding,
517            cell_bounds.right() - text_x,
518            cell_bounds.height() - kTextVerticalPadding * 2,
519            TableColumnAlignmentToCanvasAlignment(
520                visible_columns_[j].column.alignment));
521      }
522    }
523  }
524
525  if (!grouper_ || region.min_column > 0)
526    return;
527
528  const SkColor grouping_color = GetNativeTheme()->GetSystemColor(
529      ui::NativeTheme::kColorId_TableGroupingIndicatorColor);
530  SkPaint grouping_paint;
531  grouping_paint.setColor(grouping_color);
532  grouping_paint.setStyle(SkPaint::kFill_Style);
533  grouping_paint.setAntiAlias(true);
534  const int group_indicator_x = GetMirroredXInView(GetCellBounds(0, 0).x() +
535      kTextHorizontalPadding + kGroupingIndicatorSize / 2);
536  for (int i = region.min_row; i < region.max_row; ) {
537    const int model_index = ViewToModel(i);
538    GroupRange range;
539    grouper_->GetGroupRange(model_index, &range);
540    DCHECK_GT(range.length, 0);
541    // The order of rows in a group is consistent regardless of sort, so it's ok
542    // to do this calculation.
543    const int start = i - (model_index - range.start);
544    const int last = start + range.length - 1;
545    const gfx::Rect start_cell_bounds(GetCellBounds(start, 0));
546    if (start != last) {
547      const gfx::Rect last_cell_bounds(GetCellBounds(last, 0));
548      canvas->FillRect(gfx::Rect(
549                           group_indicator_x - kGroupingIndicatorSize / 2,
550                           start_cell_bounds.CenterPoint().y(),
551                           kGroupingIndicatorSize,
552                           last_cell_bounds.y() - start_cell_bounds.y()),
553                       grouping_color);
554      canvas->DrawCircle(gfx::Point(group_indicator_x,
555                                    last_cell_bounds.CenterPoint().y()),
556                         kGroupingIndicatorSize / 2, grouping_paint);
557    }
558    canvas->DrawCircle(gfx::Point(group_indicator_x,
559                                  start_cell_bounds.CenterPoint().y()),
560                       kGroupingIndicatorSize / 2, grouping_paint);
561    i = last + 1;
562  }
563}
564
565void TableView::OnFocus() {
566  SchedulePaintForSelection();
567}
568
569void TableView::OnBlur() {
570  SchedulePaintForSelection();
571}
572
573void TableView::NumRowsChanged() {
574  SortItemsAndUpdateMapping();
575  PreferredSizeChanged();
576  SchedulePaint();
577}
578
579void TableView::SetSortDescriptors(const SortDescriptors& sort_descriptors) {
580  sort_descriptors_ = sort_descriptors;
581  SortItemsAndUpdateMapping();
582  if (header_)
583    header_->SchedulePaint();
584}
585
586void TableView::SortItemsAndUpdateMapping() {
587  if (!is_sorted()) {
588    view_to_model_.clear();
589    model_to_view_.clear();
590  } else {
591    const int row_count = RowCount();
592    view_to_model_.resize(row_count);
593    model_to_view_.resize(row_count);
594    for (int i = 0; i < row_count; ++i)
595      view_to_model_[i] = i;
596    if (grouper_) {
597      GroupSortHelper sort_helper(this);
598      GetModelIndexToRangeStart(grouper_, RowCount(),
599                                &sort_helper.model_index_to_range_start);
600      std::sort(view_to_model_.begin(), view_to_model_.end(), sort_helper);
601    } else {
602      std::sort(view_to_model_.begin(), view_to_model_.end(), SortHelper(this));
603    }
604    for (int i = 0; i < row_count; ++i)
605      model_to_view_[view_to_model_[i]] = i;
606    model_->ClearCollator();
607  }
608  SchedulePaint();
609}
610
611int TableView::CompareRows(int model_row1, int model_row2) {
612  const int sort_result = model_->CompareValues(
613      model_row1, model_row2, sort_descriptors_[0].column_id);
614  if (sort_result == 0 && sort_descriptors_.size() > 1) {
615    // Try the secondary sort.
616    return SwapCompareResult(
617        model_->CompareValues(model_row1, model_row2,
618                              sort_descriptors_[1].column_id),
619        sort_descriptors_[1].ascending);
620  }
621  return SwapCompareResult(sort_result, sort_descriptors_[0].ascending);
622}
623
624gfx::Rect TableView::GetRowBounds(int row) const {
625  return gfx::Rect(0, row * row_height_, width(), row_height_);
626}
627
628gfx::Rect TableView::GetCellBounds(int row, int visible_column_index) const {
629  if (!header_)
630    return GetRowBounds(row);
631  const VisibleColumn& vis_col(visible_columns_[visible_column_index]);
632  return gfx::Rect(vis_col.x, row * row_height_, vis_col.width, row_height_);
633}
634
635void TableView::AdjustCellBoundsForText(int visible_column_index,
636                                        gfx::Rect* bounds) const {
637  int text_x = kTextHorizontalPadding + bounds->x();
638  if (visible_column_index == 0) {
639    if (grouper_)
640      text_x += kGroupingIndicatorSize + kTextHorizontalPadding;
641    if (table_type_ == ICON_AND_TEXT)
642      text_x += kImageSize + kTextHorizontalPadding;
643  }
644  bounds->set_x(text_x);
645  bounds->set_width(
646      std::max(0, bounds->right() - kTextHorizontalPadding - text_x));
647}
648
649void TableView::CreateHeaderIfNecessary() {
650  // Only create a header if there is more than one column or the title of the
651  // only column is not empty.
652  if (header_ || (columns_.size() == 1 && columns_[0].title.empty()))
653    return;
654
655  header_ = new TableHeader(this);
656}
657
658void TableView::UpdateVisibleColumnSizes() {
659  if (!header_)
660    return;
661
662  std::vector<ui::TableColumn> columns;
663  for (size_t i = 0; i < visible_columns_.size(); ++i)
664    columns.push_back(visible_columns_[i].column);
665
666  int first_column_padding = 0;
667  if (table_type_ == ICON_AND_TEXT && header_)
668    first_column_padding += kImageSize + kTextHorizontalPadding;
669  if (grouper_)
670    first_column_padding += kGroupingIndicatorSize + kTextHorizontalPadding;
671
672  std::vector<int> sizes = views::CalculateTableColumnSizes(
673      layout_width_, first_column_padding, header_->font(), font_,
674      std::max(kTextHorizontalPadding, TableHeader::kHorizontalPadding) * 2,
675      TableHeader::kSortIndicatorWidth, columns, model_);
676  DCHECK_EQ(visible_columns_.size(), sizes.size());
677  int x = 0;
678  for (size_t i = 0; i < visible_columns_.size(); ++i) {
679    visible_columns_[i].x = x;
680    visible_columns_[i].width = sizes[i];
681    x += sizes[i];
682  }
683}
684
685TableView::PaintRegion TableView::GetPaintRegion(
686    const gfx::Rect& bounds) const {
687  DCHECK(!visible_columns_.empty());
688  DCHECK(RowCount());
689
690  PaintRegion region;
691  region.min_row = std::min(RowCount() - 1,
692                            std::max(0, bounds.y() / row_height_));
693  region.max_row = bounds.bottom() / row_height_;
694  if (bounds.bottom() % row_height_ != 0)
695    region.max_row++;
696  region.max_row = std::min(region.max_row, RowCount());
697
698  if (!header_) {
699    region.max_column = 1;
700    return region;
701  }
702
703  const int paint_x = GetMirroredXForRect(bounds);
704  const int paint_max_x = paint_x + bounds.width();
705  region.min_column = -1;
706  region.max_column = visible_columns_.size();
707  for (size_t i = 0; i < visible_columns_.size(); ++i) {
708    int max_x = visible_columns_[i].x + visible_columns_[i].width;
709    if (region.min_column == -1 && max_x >= paint_x)
710      region.min_column = static_cast<int>(i);
711    if (region.min_column != -1 && visible_columns_[i].x >= paint_max_x) {
712      region.max_column = i;
713      break;
714    }
715  }
716  return region;
717}
718
719gfx::Rect TableView::GetPaintBounds(gfx::Canvas* canvas) const {
720  SkRect sk_clip_rect;
721  if (canvas->sk_canvas()->getClipBounds(&sk_clip_rect))
722    return gfx::ToEnclosingRect(gfx::SkRectToRectF(sk_clip_rect));
723  return GetVisibleBounds();
724}
725
726void TableView::SchedulePaintForSelection() {
727  if (selection_model_.size() == 1) {
728    const int first_model_row = FirstSelectedRow();
729    SchedulePaintInRect(GetRowBounds(ModelToView(first_model_row)));
730    if (first_model_row != selection_model_.active())
731      SchedulePaintInRect(GetRowBounds(ModelToView(selection_model_.active())));
732  } else if (selection_model_.size() > 1) {
733    SchedulePaint();
734  }
735}
736
737ui::TableColumn TableView::FindColumnByID(int id) const {
738  for (size_t i = 0; i < columns_.size(); ++i) {
739    if (columns_[i].id == id)
740      return columns_[i];
741  }
742  NOTREACHED();
743  return ui::TableColumn();
744}
745
746void TableView::SelectByViewIndex(int view_index) {
747  ui::ListSelectionModel new_selection;
748  if (view_index != -1) {
749    SelectRowsInRangeFrom(view_index, true, &new_selection);
750    new_selection.set_anchor(ViewToModel(view_index));
751    new_selection.set_active(ViewToModel(view_index));
752  }
753
754  SetSelectionModel(new_selection);
755}
756
757void TableView::SetSelectionModel(const ui::ListSelectionModel& new_selection) {
758  if (new_selection.Equals(selection_model_))
759    return;
760
761  SchedulePaintForSelection();
762  selection_model_.Copy(new_selection);
763  SchedulePaintForSelection();
764
765  // Scroll the group for the active item to visible.
766  if (selection_model_.active() != -1) {
767    gfx::Rect vis_rect(GetVisibleBounds());
768    const GroupRange range(GetGroupRange(selection_model_.active()));
769    const int start_y = GetRowBounds(ModelToView(range.start)).y();
770    const int end_y =
771        GetRowBounds(ModelToView(range.start + range.length - 1)).bottom();
772    vis_rect.set_y(start_y);
773    vis_rect.set_height(end_y - start_y);
774    ScrollRectToVisible(vis_rect);
775  }
776
777  if (table_view_observer_)
778    table_view_observer_->OnSelectionChanged();
779}
780
781void TableView::AdvanceSelection(AdvanceDirection direction) {
782  if (selection_model_.active() == -1) {
783    SelectByViewIndex(0);
784    return;
785  }
786  int view_index = ModelToView(selection_model_.active());
787  if (direction == ADVANCE_DECREMENT)
788    view_index = std::max(0, view_index - 1);
789  else
790    view_index = std::min(RowCount() - 1, view_index + 1);
791  SelectByViewIndex(view_index);
792}
793
794void TableView::ConfigureSelectionModelForEvent(
795    const ui::MouseEvent& event,
796    ui::ListSelectionModel* model) const {
797  const int view_index = event.y() / row_height_;
798  DCHECK(view_index >= 0 && view_index < RowCount());
799
800  if (selection_model_.anchor() == -1 ||
801      single_selection_ ||
802      (!event.IsControlDown() && !event.IsShiftDown())) {
803    SelectRowsInRangeFrom(view_index, true, model);
804    model->set_anchor(ViewToModel(view_index));
805    model->set_active(ViewToModel(view_index));
806    return;
807  }
808  if ((event.IsControlDown() && event.IsShiftDown()) || event.IsShiftDown()) {
809    // control-shift: copy existing model and make sure rows between anchor and
810    // |view_index| are selected.
811    // shift: reset selection so that only rows between anchor and |view_index|
812    // are selected.
813    if (event.IsControlDown() && event.IsShiftDown())
814      model->Copy(selection_model_);
815    else
816      model->set_anchor(selection_model_.anchor());
817    for (int i = std::min(view_index, ModelToView(model->anchor())),
818             end = std::max(view_index, ModelToView(model->anchor()));
819         i <= end; ++i) {
820      SelectRowsInRangeFrom(i, true, model);
821    }
822    model->set_active(ViewToModel(view_index));
823  } else {
824    DCHECK(event.IsControlDown());
825    // Toggle the selection state of |view_index| and set the anchor/active to
826    // it and don't change the state of any other rows.
827    model->Copy(selection_model_);
828    model->set_anchor(ViewToModel(view_index));
829    model->set_active(ViewToModel(view_index));
830    SelectRowsInRangeFrom(view_index,
831                          !model->IsSelected(ViewToModel(view_index)),
832                          model);
833  }
834}
835
836void TableView::SelectRowsInRangeFrom(int view_index,
837                                      bool select,
838                                      ui::ListSelectionModel* model) const {
839  const GroupRange range(GetGroupRange(ViewToModel(view_index)));
840  for (int i = 0; i < range.length; ++i) {
841    if (select)
842      model->AddIndexToSelection(range.start + i);
843    else
844      model->RemoveIndexFromSelection(range.start + i);
845  }
846}
847
848GroupRange TableView::GetGroupRange(int model_index) const {
849  GroupRange range;
850  if (grouper_) {
851    grouper_->GetGroupRange(model_index, &range);
852  } else {
853    range.start = model_index;
854    range.length = 1;
855  }
856  return range;
857}
858
859bool TableView::GetTooltipImpl(const gfx::Point& location,
860                               string16* tooltip,
861                               gfx::Point* tooltip_origin) const {
862  const int row = location.y() / row_height_;
863  if (row < 0 || row >= RowCount() || visible_columns_.empty())
864    return false;
865
866  const int x = GetMirroredXInView(location.x());
867  const int column = GetClosestVisibleColumnIndex(this, x);
868  if (x < visible_columns_[column].x ||
869      x > (visible_columns_[column].x + visible_columns_[column].width))
870    return false;
871
872  const string16 text(model_->GetText(ViewToModel(row),
873                                      visible_columns_[column].column.id));
874  if (text.empty())
875    return false;
876
877  gfx::Rect cell_bounds(GetCellBounds(row, column));
878  AdjustCellBoundsForText(column, &cell_bounds);
879  const int right = std::min(GetVisibleBounds().right(), cell_bounds.right());
880  if (right > cell_bounds.x() &&
881      font_.GetStringWidth(text) <= (right - cell_bounds.x()))
882    return false;
883
884  if (tooltip)
885    *tooltip = text;
886  if (tooltip_origin) {
887    tooltip_origin->SetPoint(cell_bounds.x(),
888                             cell_bounds.y() + kTextVerticalPadding);
889  }
890  return true;
891}
892
893}  // namespace views
894