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