- add sources.
[platform/framework/web/crosswalk.git] / src / ui / views / controls / table / table_view.cc
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/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).
25 static const int kTextVerticalPadding = 3;
26 static const int kTextHorizontalPadding = 6;
27
28 // Size of images.
29 static const int kImageSize = 16;
30
31 static const int kGroupingIndicatorSize = 6;
32
33 namespace views {
34
35 namespace {
36
37 // Returns result, unless ascending is false in which case -result is returned.
38 int SwapCompareResult(int result, bool ascending) {
39   return ascending ? result : -result;
40 }
41
42 // Populates |model_index_to_range_start| based on the |grouper|.
43 void 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.
58 ui::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.
65 ui::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.
73 struct 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.
86 struct 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
104 TableView::VisibleColumn::VisibleColumn() : x(0), width(0) {}
105
106 TableView::VisibleColumn::~VisibleColumn() {}
107
108 TableView::PaintRegion::PaintRegion()
109     : min_row(0),
110       max_row(0),
111       min_column(0),
112       max_column(0) {
113 }
114
115 TableView::PaintRegion::~PaintRegion() {}
116
117 TableView::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
141 TableView::~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.
148 void 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
160 View* 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
169 void TableView::SetRowBackgroundPainter(
170     scoped_ptr<TableViewRowBackgroundPainter> painter) {
171   row_background_painter_ = painter.Pass();
172 }
173
174 void TableView::SetGrouper(TableGrouper* grouper) {
175   grouper_ = grouper;
176   SortItemsAndUpdateMapping();
177 }
178
179 int TableView::RowCount() const {
180   return model_ ? model_->RowCount() : 0;
181 }
182
183 int TableView::SelectedRowCount() {
184   return static_cast<int>(selection_model_.size());
185 }
186
187 void TableView::Select(int model_row) {
188   if (!model_)
189     return;
190
191   SelectByViewIndex(model_row == -1 ? -1 : ModelToView(model_row));
192 }
193
194 int TableView::FirstSelectedRow() {
195   return SelectedRowCount() == 0 ? -1 : selection_model_.selected_indices()[0];
196 }
197
198 void 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
221 void 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
240 bool 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
248 void TableView::AddColumn(const ui::TableColumn& col) {
249   DCHECK(!HasColumn(col.id));
250   columns_.push_back(col);
251 }
252
253 bool 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
261 void 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
275 int 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
284 int 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
293 void 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
320 gfx::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
327 bool 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
370 bool 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
392 void TableView::OnGestureEvent(ui::GestureEvent* event) {
393   if (event->type() != ui::ET_GESTURE_TAP)
394     return;
395
396   const int row = event->y() / row_height_;
397   if (row < 0 || row >= RowCount())
398     return;
399
400   event->StopPropagation();
401   ui::ListSelectionModel new_model;
402   ConfigureSelectionModelForEvent(*event, &new_model);
403   SetSelectionModel(new_model);
404 }
405
406 bool TableView::GetTooltipText(const gfx::Point& p,
407                                string16* tooltip) const {
408   return GetTooltipImpl(p, tooltip, NULL);
409 }
410
411 bool TableView::GetTooltipTextOrigin(const gfx::Point& p,
412                                      gfx::Point* loc) const {
413   return GetTooltipImpl(p, NULL, loc);
414 }
415
416 void TableView::OnModelChanged() {
417   selection_model_.Clear();
418   NumRowsChanged();
419 }
420
421 void TableView::OnItemsChanged(int start, int length) {
422   SortItemsAndUpdateMapping();
423 }
424
425 void TableView::OnItemsAdded(int start, int length) {
426   for (int i = 0; i < length; ++i)
427     selection_model_.IncrementFrom(start);
428   NumRowsChanged();
429 }
430
431 void TableView::OnItemsRemoved(int start, int length) {
432   // Determine the currently selected index in terms of the view. We inline the
433   // implementation here since ViewToModel() has DCHECKs that fail since the
434   // model has changed but |model_to_view_| has not been updated yet.
435   const int previously_selected_model_index = FirstSelectedRow();
436   int previously_selected_view_index = previously_selected_model_index;
437   if (previously_selected_model_index != -1 && is_sorted())
438     previously_selected_view_index =
439         model_to_view_[previously_selected_model_index];
440   for (int i = 0; i < length; ++i)
441     selection_model_.DecrementFrom(start);
442   NumRowsChanged();
443   // If the selection was empty and is no longer empty select the same visual
444   // index.
445   if (selection_model_.empty() && previously_selected_view_index != -1 &&
446       RowCount()) {
447     selection_model_.SetSelectedIndex(
448         ViewToModel(std::min(RowCount() - 1, previously_selected_view_index)));
449   }
450   if (table_view_observer_)
451     table_view_observer_->OnSelectionChanged();
452 }
453
454 gfx::Point TableView::GetKeyboardContextMenuLocation() {
455   int first_selected = FirstSelectedRow();
456   gfx::Rect vis_bounds(GetVisibleBounds());
457   int y = vis_bounds.height() / 2;
458   if (first_selected != -1) {
459     gfx::Rect cell_bounds(GetRowBounds(first_selected));
460     if (cell_bounds.bottom() >= vis_bounds.y() &&
461         cell_bounds.bottom() < vis_bounds.bottom()) {
462       y = cell_bounds.bottom();
463     }
464   }
465   gfx::Point screen_loc(0, y);
466   if (base::i18n::IsRTL())
467     screen_loc.set_x(width());
468   ConvertPointToScreen(this, &screen_loc);
469   return screen_loc;
470 }
471
472 void TableView::OnPaint(gfx::Canvas* canvas) {
473   // Don't invoke View::OnPaint so that we can render our own focus border.
474
475   canvas->DrawColor(GetNativeTheme()->GetSystemColor(
476                         ui::NativeTheme::kColorId_TableBackground));
477
478   if (!RowCount() || visible_columns_.empty())
479     return;
480
481   const PaintRegion region(GetPaintRegion(GetPaintBounds(canvas)));
482   if (region.min_column == -1)
483     return;  // No need to paint anything.
484
485   const SkColor selected_bg_color = GetNativeTheme()->GetSystemColor(
486       text_background_color_id(HasFocus()));
487   const SkColor fg_color = GetNativeTheme()->GetSystemColor(
488       ui::NativeTheme::kColorId_TableText);
489   const SkColor selected_fg_color = GetNativeTheme()->GetSystemColor(
490       selected_text_color_id(HasFocus()));
491   for (int i = region.min_row; i < region.max_row; ++i) {
492     const int model_index = ViewToModel(i);
493     const bool is_selected = selection_model_.IsSelected(model_index);
494     if (is_selected) {
495       canvas->FillRect(GetRowBounds(i), selected_bg_color);
496     } else if (row_background_painter_) {
497       row_background_painter_->PaintRowBackground(model_index,
498                                                   GetRowBounds(i),
499                                                   canvas);
500     }
501     if (selection_model_.active() == i && HasFocus())
502       canvas->DrawFocusRect(GetRowBounds(i));
503     for (int j = region.min_column; j < region.max_column; ++j) {
504       const gfx::Rect cell_bounds(GetCellBounds(i, j));
505       int text_x = kTextHorizontalPadding + cell_bounds.x();
506
507       // Provide space for the grouping indicator, but draw it separately.
508       if (j == 0 && grouper_)
509         text_x += kGroupingIndicatorSize + kTextHorizontalPadding;
510
511       // Always paint the icon in the first visible column.
512       if (j == 0 && table_type_ == ICON_AND_TEXT) {
513         gfx::ImageSkia image = model_->GetIcon(model_index);
514         if (!image.isNull()) {
515           int image_x = GetMirroredXWithWidthInView(text_x, kImageSize);
516           canvas->DrawImageInt(
517               image, 0, 0, image.width(), image.height(),
518               image_x,
519               cell_bounds.y() + (cell_bounds.height() - kImageSize) / 2,
520               kImageSize, kImageSize, true);
521         }
522         text_x += kImageSize + kTextHorizontalPadding;
523       }
524       if (text_x < cell_bounds.right() - kTextHorizontalPadding) {
525         canvas->DrawStringInt(
526             model_->GetText(model_index, visible_columns_[j].column.id), font_,
527             is_selected ? selected_fg_color : fg_color,
528             GetMirroredXWithWidthInView(text_x, cell_bounds.right() - text_x -
529                                         kTextHorizontalPadding),
530             cell_bounds.y() + kTextVerticalPadding,
531             cell_bounds.right() - text_x,
532             cell_bounds.height() - kTextVerticalPadding * 2,
533             TableColumnAlignmentToCanvasAlignment(
534                 visible_columns_[j].column.alignment));
535       }
536     }
537   }
538
539   if (!grouper_ || region.min_column > 0)
540     return;
541
542   const SkColor grouping_color = GetNativeTheme()->GetSystemColor(
543       ui::NativeTheme::kColorId_TableGroupingIndicatorColor);
544   SkPaint grouping_paint;
545   grouping_paint.setColor(grouping_color);
546   grouping_paint.setStyle(SkPaint::kFill_Style);
547   grouping_paint.setAntiAlias(true);
548   const int group_indicator_x = GetMirroredXInView(GetCellBounds(0, 0).x() +
549       kTextHorizontalPadding + kGroupingIndicatorSize / 2);
550   for (int i = region.min_row; i < region.max_row; ) {
551     const int model_index = ViewToModel(i);
552     GroupRange range;
553     grouper_->GetGroupRange(model_index, &range);
554     DCHECK_GT(range.length, 0);
555     // The order of rows in a group is consistent regardless of sort, so it's ok
556     // to do this calculation.
557     const int start = i - (model_index - range.start);
558     const int last = start + range.length - 1;
559     const gfx::Rect start_cell_bounds(GetCellBounds(start, 0));
560     if (start != last) {
561       const gfx::Rect last_cell_bounds(GetCellBounds(last, 0));
562       canvas->FillRect(gfx::Rect(
563                            group_indicator_x - kGroupingIndicatorSize / 2,
564                            start_cell_bounds.CenterPoint().y(),
565                            kGroupingIndicatorSize,
566                            last_cell_bounds.y() - start_cell_bounds.y()),
567                        grouping_color);
568       canvas->DrawCircle(gfx::Point(group_indicator_x,
569                                     last_cell_bounds.CenterPoint().y()),
570                          kGroupingIndicatorSize / 2, grouping_paint);
571     }
572     canvas->DrawCircle(gfx::Point(group_indicator_x,
573                                   start_cell_bounds.CenterPoint().y()),
574                        kGroupingIndicatorSize / 2, grouping_paint);
575     i = last + 1;
576   }
577 }
578
579 void TableView::OnFocus() {
580   SchedulePaintForSelection();
581 }
582
583 void TableView::OnBlur() {
584   SchedulePaintForSelection();
585 }
586
587 void TableView::NumRowsChanged() {
588   SortItemsAndUpdateMapping();
589   PreferredSizeChanged();
590   SchedulePaint();
591 }
592
593 void TableView::SetSortDescriptors(const SortDescriptors& sort_descriptors) {
594   sort_descriptors_ = sort_descriptors;
595   SortItemsAndUpdateMapping();
596   if (header_)
597     header_->SchedulePaint();
598 }
599
600 void TableView::SortItemsAndUpdateMapping() {
601   if (!is_sorted()) {
602     view_to_model_.clear();
603     model_to_view_.clear();
604   } else {
605     const int row_count = RowCount();
606     view_to_model_.resize(row_count);
607     model_to_view_.resize(row_count);
608     for (int i = 0; i < row_count; ++i)
609       view_to_model_[i] = i;
610     if (grouper_) {
611       GroupSortHelper sort_helper(this);
612       GetModelIndexToRangeStart(grouper_, RowCount(),
613                                 &sort_helper.model_index_to_range_start);
614       std::sort(view_to_model_.begin(), view_to_model_.end(), sort_helper);
615     } else {
616       std::sort(view_to_model_.begin(), view_to_model_.end(), SortHelper(this));
617     }
618     for (int i = 0; i < row_count; ++i)
619       model_to_view_[view_to_model_[i]] = i;
620     model_->ClearCollator();
621   }
622   SchedulePaint();
623 }
624
625 int TableView::CompareRows(int model_row1, int model_row2) {
626   const int sort_result = model_->CompareValues(
627       model_row1, model_row2, sort_descriptors_[0].column_id);
628   if (sort_result == 0 && sort_descriptors_.size() > 1) {
629     // Try the secondary sort.
630     return SwapCompareResult(
631         model_->CompareValues(model_row1, model_row2,
632                               sort_descriptors_[1].column_id),
633         sort_descriptors_[1].ascending);
634   }
635   return SwapCompareResult(sort_result, sort_descriptors_[0].ascending);
636 }
637
638 gfx::Rect TableView::GetRowBounds(int row) const {
639   return gfx::Rect(0, row * row_height_, width(), row_height_);
640 }
641
642 gfx::Rect TableView::GetCellBounds(int row, int visible_column_index) const {
643   if (!header_)
644     return GetRowBounds(row);
645   const VisibleColumn& vis_col(visible_columns_[visible_column_index]);
646   return gfx::Rect(vis_col.x, row * row_height_, vis_col.width, row_height_);
647 }
648
649 void TableView::AdjustCellBoundsForText(int visible_column_index,
650                                         gfx::Rect* bounds) const {
651   int text_x = kTextHorizontalPadding + bounds->x();
652   if (visible_column_index == 0) {
653     if (grouper_)
654       text_x += kGroupingIndicatorSize + kTextHorizontalPadding;
655     if (table_type_ == ICON_AND_TEXT)
656       text_x += kImageSize + kTextHorizontalPadding;
657   }
658   bounds->set_x(text_x);
659   bounds->set_width(
660       std::max(0, bounds->right() - kTextHorizontalPadding - text_x));
661 }
662
663 void TableView::CreateHeaderIfNecessary() {
664   // Only create a header if there is more than one column or the title of the
665   // only column is not empty.
666   if (header_ || (columns_.size() == 1 && columns_[0].title.empty()))
667     return;
668
669   header_ = new TableHeader(this);
670 }
671
672 void TableView::UpdateVisibleColumnSizes() {
673   if (!header_)
674     return;
675
676   std::vector<ui::TableColumn> columns;
677   for (size_t i = 0; i < visible_columns_.size(); ++i)
678     columns.push_back(visible_columns_[i].column);
679
680   int first_column_padding = 0;
681   if (table_type_ == ICON_AND_TEXT && header_)
682     first_column_padding += kImageSize + kTextHorizontalPadding;
683   if (grouper_)
684     first_column_padding += kGroupingIndicatorSize + kTextHorizontalPadding;
685
686   std::vector<int> sizes = views::CalculateTableColumnSizes(
687       layout_width_, first_column_padding, header_->font(), font_,
688       std::max(kTextHorizontalPadding, TableHeader::kHorizontalPadding) * 2,
689       TableHeader::kSortIndicatorWidth, columns, model_);
690   DCHECK_EQ(visible_columns_.size(), sizes.size());
691   int x = 0;
692   for (size_t i = 0; i < visible_columns_.size(); ++i) {
693     visible_columns_[i].x = x;
694     visible_columns_[i].width = sizes[i];
695     x += sizes[i];
696   }
697 }
698
699 TableView::PaintRegion TableView::GetPaintRegion(
700     const gfx::Rect& bounds) const {
701   DCHECK(!visible_columns_.empty());
702   DCHECK(RowCount());
703
704   PaintRegion region;
705   region.min_row = std::min(RowCount() - 1,
706                             std::max(0, bounds.y() / row_height_));
707   region.max_row = bounds.bottom() / row_height_;
708   if (bounds.bottom() % row_height_ != 0)
709     region.max_row++;
710   region.max_row = std::min(region.max_row, RowCount());
711
712   if (!header_) {
713     region.max_column = 1;
714     return region;
715   }
716
717   const int paint_x = GetMirroredXForRect(bounds);
718   const int paint_max_x = paint_x + bounds.width();
719   region.min_column = -1;
720   region.max_column = visible_columns_.size();
721   for (size_t i = 0; i < visible_columns_.size(); ++i) {
722     int max_x = visible_columns_[i].x + visible_columns_[i].width;
723     if (region.min_column == -1 && max_x >= paint_x)
724       region.min_column = static_cast<int>(i);
725     if (region.min_column != -1 && visible_columns_[i].x >= paint_max_x) {
726       region.max_column = i;
727       break;
728     }
729   }
730   return region;
731 }
732
733 gfx::Rect TableView::GetPaintBounds(gfx::Canvas* canvas) const {
734   SkRect sk_clip_rect;
735   if (canvas->sk_canvas()->getClipBounds(&sk_clip_rect))
736     return gfx::ToEnclosingRect(gfx::SkRectToRectF(sk_clip_rect));
737   return GetVisibleBounds();
738 }
739
740 void TableView::SchedulePaintForSelection() {
741   if (selection_model_.size() == 1) {
742     const int first_model_row = FirstSelectedRow();
743     SchedulePaintInRect(GetRowBounds(ModelToView(first_model_row)));
744     if (first_model_row != selection_model_.active())
745       SchedulePaintInRect(GetRowBounds(ModelToView(selection_model_.active())));
746   } else if (selection_model_.size() > 1) {
747     SchedulePaint();
748   }
749 }
750
751 ui::TableColumn TableView::FindColumnByID(int id) const {
752   for (size_t i = 0; i < columns_.size(); ++i) {
753     if (columns_[i].id == id)
754       return columns_[i];
755   }
756   NOTREACHED();
757   return ui::TableColumn();
758 }
759
760 void TableView::SelectByViewIndex(int view_index) {
761   ui::ListSelectionModel new_selection;
762   if (view_index != -1) {
763     SelectRowsInRangeFrom(view_index, true, &new_selection);
764     new_selection.set_anchor(ViewToModel(view_index));
765     new_selection.set_active(ViewToModel(view_index));
766   }
767
768   SetSelectionModel(new_selection);
769 }
770
771 void TableView::SetSelectionModel(const ui::ListSelectionModel& new_selection) {
772   if (new_selection.Equals(selection_model_))
773     return;
774
775   SchedulePaintForSelection();
776   selection_model_.Copy(new_selection);
777   SchedulePaintForSelection();
778
779   // Scroll the group for the active item to visible.
780   if (selection_model_.active() != -1) {
781     gfx::Rect vis_rect(GetVisibleBounds());
782     const GroupRange range(GetGroupRange(selection_model_.active()));
783     const int start_y = GetRowBounds(ModelToView(range.start)).y();
784     const int end_y =
785         GetRowBounds(ModelToView(range.start + range.length - 1)).bottom();
786     vis_rect.set_y(start_y);
787     vis_rect.set_height(end_y - start_y);
788     ScrollRectToVisible(vis_rect);
789   }
790
791   if (table_view_observer_)
792     table_view_observer_->OnSelectionChanged();
793 }
794
795 void TableView::AdvanceSelection(AdvanceDirection direction) {
796   if (selection_model_.active() == -1) {
797     SelectByViewIndex(0);
798     return;
799   }
800   int view_index = ModelToView(selection_model_.active());
801   if (direction == ADVANCE_DECREMENT)
802     view_index = std::max(0, view_index - 1);
803   else
804     view_index = std::min(RowCount() - 1, view_index + 1);
805   SelectByViewIndex(view_index);
806 }
807
808 void TableView::ConfigureSelectionModelForEvent(
809     const ui::LocatedEvent& event,
810     ui::ListSelectionModel* model) const {
811   const int view_index = event.y() / row_height_;
812   DCHECK(view_index >= 0 && view_index < RowCount());
813
814   if (selection_model_.anchor() == -1 ||
815       single_selection_ ||
816       (!event.IsControlDown() && !event.IsShiftDown())) {
817     SelectRowsInRangeFrom(view_index, true, model);
818     model->set_anchor(ViewToModel(view_index));
819     model->set_active(ViewToModel(view_index));
820     return;
821   }
822   if ((event.IsControlDown() && event.IsShiftDown()) || event.IsShiftDown()) {
823     // control-shift: copy existing model and make sure rows between anchor and
824     // |view_index| are selected.
825     // shift: reset selection so that only rows between anchor and |view_index|
826     // are selected.
827     if (event.IsControlDown() && event.IsShiftDown())
828       model->Copy(selection_model_);
829     else
830       model->set_anchor(selection_model_.anchor());
831     for (int i = std::min(view_index, ModelToView(model->anchor())),
832              end = std::max(view_index, ModelToView(model->anchor()));
833          i <= end; ++i) {
834       SelectRowsInRangeFrom(i, true, model);
835     }
836     model->set_active(ViewToModel(view_index));
837   } else {
838     DCHECK(event.IsControlDown());
839     // Toggle the selection state of |view_index| and set the anchor/active to
840     // it and don't change the state of any other rows.
841     model->Copy(selection_model_);
842     model->set_anchor(ViewToModel(view_index));
843     model->set_active(ViewToModel(view_index));
844     SelectRowsInRangeFrom(view_index,
845                           !model->IsSelected(ViewToModel(view_index)),
846                           model);
847   }
848 }
849
850 void TableView::SelectRowsInRangeFrom(int view_index,
851                                       bool select,
852                                       ui::ListSelectionModel* model) const {
853   const GroupRange range(GetGroupRange(ViewToModel(view_index)));
854   for (int i = 0; i < range.length; ++i) {
855     if (select)
856       model->AddIndexToSelection(range.start + i);
857     else
858       model->RemoveIndexFromSelection(range.start + i);
859   }
860 }
861
862 GroupRange TableView::GetGroupRange(int model_index) const {
863   GroupRange range;
864   if (grouper_) {
865     grouper_->GetGroupRange(model_index, &range);
866   } else {
867     range.start = model_index;
868     range.length = 1;
869   }
870   return range;
871 }
872
873 bool TableView::GetTooltipImpl(const gfx::Point& location,
874                                string16* tooltip,
875                                gfx::Point* tooltip_origin) const {
876   const int row = location.y() / row_height_;
877   if (row < 0 || row >= RowCount() || visible_columns_.empty())
878     return false;
879
880   const int x = GetMirroredXInView(location.x());
881   const int column = GetClosestVisibleColumnIndex(this, x);
882   if (x < visible_columns_[column].x ||
883       x > (visible_columns_[column].x + visible_columns_[column].width))
884     return false;
885
886   const string16 text(model_->GetText(ViewToModel(row),
887                                       visible_columns_[column].column.id));
888   if (text.empty())
889     return false;
890
891   gfx::Rect cell_bounds(GetCellBounds(row, column));
892   AdjustCellBoundsForText(column, &cell_bounds);
893   const int right = std::min(GetVisibleBounds().right(), cell_bounds.right());
894   if (right > cell_bounds.x() &&
895       font_.GetStringWidth(text) <= (right - cell_bounds.x()))
896     return false;
897
898   if (tooltip)
899     *tooltip = text;
900   if (tooltip_origin) {
901     tooltip_origin->SetPoint(cell_bounds.x(),
902                              cell_bounds.y() + kTextVerticalPadding);
903   }
904   return true;
905 }
906
907 }  // namespace views